This exercise was originally completed in Python, during my completion of Week 4, Case Study 1 of the free access version of Harvard EdX course “Using Python for Research” in the summer of 2022. This translation to R was performed in the Spring of 2023.

The Tidyverse, Janitor, sjmisc, rBokeh and ComplexHeatmap packages were installed prior to generation, and this write-up was created as an Rnotebook with Rstudio.

The use of ComplexHeatmap has a requested citation from the creator:

Gu, Z. (2016) Complex heatmaps reveal patterns and correlations in multidimensional genomic data. Bioinformatics. DOI: 10.1093/bioinformatics/btw313.

Importation of Data

Libraries used for this data set were imported.

Rstudio has a feature which makes importation of data from a CSV file to a tibble user friendly. This example write up will skip that step to demonstrate the ability to manually assess imported data.

SuppressMessages was used to suppress default console warning messages that these features were not used.

Introductory Demonstration of Bokeh

Bokeh allows data to be visualized with interactive elements that can render as .html files. The following demonstration is to show a general use of Bokeh by creating a 5x5 grid composed of two alternating colors. There is no particular reason to select a 5x5 plot aside from being an easily manageable size; it is unrelated to our Whiskies DataSet.

We can indicate the values we want for both our x and y axes, and the colors we would like to use for our grid. Bokeh takes hex values for colors. #03f0fc is a bright electric blue, while #fca103 is a vibrant orange. Selection is based on personal preference and readability. Since colors can be fully customized, this provides great adaptability for modification of Bokeh plots for maximum readability.

We are able to use the built in expand.grid() function in R to create cartesian coordinates, and we rename our columns to clarify which values are our xs and which are ys.

We then create all the color values for our Bokeh plot, stored in bokehColors, using a simple for loop. This loop iterates over all values in grid, and assigns them alternating colors from colors using a modulo operator and indexing.

Finally, we determine our transparency values (alphas) of each point, where 0 is completely transparent and 1 is entirely opaque. The built in seq() function of R allows us to create an evently spaced gradient of values, which will result in decreasing transparency diagonally from bottom left to top right: the bottom leftmost value (1,1) will be transparent, while the top rightmost value (5,5) will be entirely opaque.

Unlike Python, we do not need to declare a ColumnDataSource in R. Rather, we can simply declare and create our figure using R’s built in syntax.

Bokeh can also be utilized to plot using latitude and longitude. It plots similarly to a scatter-plot, which is particularly useful for latitude and longitude naturally taking form in a cartesian (x,y) structure. rBokeh also has the capability to automatically assign a consistent glyph and/or color structure to data points, based on categorical variables. To demonstrate, a dataframe consisting of “X”, “Y”, and “Category” values is generated, and visualized with rBokeh.

Note the change to the configuration of the hover tool: x and y coordinates are still shown when hovering over any data point, but are now labeled as “Location”. Observe how on the rendered visualization, there is no information provided by hover where a data point was not plotted.

Whiskey Exploration

The first stage of utilizing any new data set should be exploration of the data included in the set, including its source.

Cleaning Up The Data

This data set was originally provided as a csv file. We come into the data set with the expectation that each row contains data about a single whiskey produced in Scotland, with one Whiskey per distillery. Each Whiskey is “graded” on various aspects of its flavor profile (though, from the data set itself, we do not know by whom or what specific criteria may have been used for evaluation).

Exploration of the created whiskey DataFrame reveals that there are 86 included Whiskies, and 18 ‘Columns’ of Data for each Whiskey.

We can see that the first row appears to indicate column names, so we can simplify our tibble. There is a function for this in the Janitor package.

We use names(whiskey) to retrieve the names of all columns in the data set, to examine what points of data we expect to be provided about each whiskey.

We see the first two columns NA and RowID appear to be an arbitrary count.

The third column, Distillery, appears to be the name of the distillery the whiskey came from. From what we expected coming into our dataset, there is one whiskey per distillery. This means that Distillery can serve as our “key”.

We find that all flavors are included in the next 11 columns: Sweetness, Smoky, Medicinal, Tobacco, Honey, Spicy, Winey, Nutty, Malty, Fruity, and Floral.

We find that the remaining columns describe the geographic location of the distillery that produced each whiskey: Postcode, Latitude, Longitude, and Region.

We can drop both columns that appear to serve as a RowID, since we do not need them. Similarly, we will be generating values for the last column, Group, and can drop the imported values. The Dplyr package has a function for this.

Thanks to our tibble, we may notice that our columns all are storing their data as characters. This is confirmed by checking the class for all of our columns.

Luckily Dplyr has a method to quickly convert our columns that contain only numeric values to a numeric data class.

We specify which columns we would like to convert. We store the names of the flavors in flavor, and add Latitude and Longitude to a list of numeric column names.

Since we are primarily interested in the relationship between each Whiskey and its flavor profile, we can create two sub-sets of our data frame that only contains the flavors and Distillery name

Exploration (Flavors)

How are these flavor profiles characterized? In our initial examination, we noted that there appeared to be integer values in each cell relating to an aspect of the flavor profile.

We can examine these scores in a bit more detail, so we understand how these whiskies have been characterized.

First, we can inspect the maximum values of each flavor. Tobacco appears to have the lowest maximum, with a peak score of 1. No flavor has a score higher than 4.

Inspection of minimum values shows that each flavor has a minimum score of 0 except for Sweetness, which has a minimum score of 1.

Each flavor appears to be scored from 0 - 4, with no half scores. Functionally, this is a discrete numeric variable. While there is no legend provided in the data set, it may be a reasonable assumption to believe that a score of 0 indicates a lack of a flavor, while a score of 4 indicates a strong flavor, with intermediary scores dividing the resulting continuum of intensity.

We can perform some basic summary statistics to explore each of these flavors further, if desired, using the built-in summary() function in R. We create a subset of the data (flavorData) that does not have the Distillery column to avoid those values being counted.

We see that every whiskey has a rating for each flavor. The median scores appear similar to means with a cursory overview.

Exploring Linear Correlations

Pairwise Linear Correlations: Flavor to Flavor

Now that we have a general understanding of the shape and form of our data set, we can explore relationships expressed within it.

First, we’d like to examine relationships between individual flavors that compose each flavor profile.

R has a built in function for Linear Correlations: cor().

The documentation for the cor() function is worth examining: we can pass a data frame, and R will perform a pairwise correlation among columns. The flavorData subset will be most useful here, as we are comparing each flavor to the other.

We are returned a new data frame. Each column still corresponds to each flavor, but each row indicates which flavor the column flavor was associated with rather than an individual whiskey.

Values of Pearson Correlation Coefficients range from -1 to 1. Negative values indicate a negative association (indicating the flavors are less likely to appear together), while positive values indicate a positive association (indicating flavors are more likely to appear together). A value of 0 indicates no relationship. If each flavor was scored in a binary “yes/no” or 0/1 fashion, we might expect the correlation to only extend to the presence of each flavor, but because the flavors are scored on intensity, the intensity of the flavors impact our correlation as well. As such each cell value is the Pearson Correlation Coefficients of the association between the value of the column flavor, factoring in intensity, to the row flavor, also factoring in intensity. We can interpret this to state that the higher absolute value the Pearson Correlation Coefficient, the stronger the link between the intensity of the two flavors.

We store this new data frame of correlation values as flavorCorrelations.

It is important to remember that these associations do not imply direction or causality! As such, the association of “Sweetness” to “Honey” is the same as the association of “Honey” to “Sweetness”.

Based on our knowledge of Pairwise Linear Correlations and the cor() function, we can do a few checks to ensure we have an output that conforms to our theoretical expectations.

  1. We expect our data to be symmetrical with 12 columns and 12 rows. This is because we have 12 individual flavors. We can check this with the dim() command.
  1. We can expect the data along the diagonal to represent consistent values of 1.

This is because flavorCorrelations[1,1] represents the same flavor associated with itself. As each flavor is only rated once for each whiskey, it logically follows that the correlation between scores for any flavor, such as “Sweetness” to “Sweetness” or “Spicy” to “Spicy” must be 1:1.

  1. We expect the data to be mirrored symmetrically along the diagonal.

This is because association does not have a direction. As such, the correlation between “Sweetness” and “Honey”, the second and sixth flavors (at index 2 and 6 respectively) should be found to be the same regardless of the direction of comparison.

We check this by comparing the values at [2,6] and [6,2] - they should be identical. And they are, both holding a value of 0.1325581.

While this correlation data set, at 12 by 12, is manageable, this process needs to be scaleable.

The ComplexHeatmap library provides us with a robust tool for creating a heatmap of the correlation values.

As we can see, our Heatmap provides a wealth of information about our flavors at a glance.

We can check our data against our expectations at a glance: 1. We have an 12 x 12 product, reflecting that each flavor was correlated with each other flavor 2. The diagonal represents consistent values of 1, as each flavor has a 1:1 correlation with itself. 3. The data is mirrored symmetrically across the diagonal, as the associations have no direction.

In any individual flavor profile, it is unlikely for a whiskey to have “Sweetness”, while also being either “Medicinal” or “Smoky”. Conversely, a whiskey with “Body” appears to be more likely to also be considered “Winey”.

This is an interesting overview of all of the whiskies, but what else can we do with this data? #### Pairwise Linear Correlations: Distillery to Distillery (Comparison of Flavor Profiles)

The data takes on a much more interesting form for analysis when we take it’s transpose. When we take the transpose of a data frame, we “flip” the data across its diagonal - rows convert into columns, and vice versa.

In our flavorProfile data set, this organizes each column into the flavor profile of each Distillery’s product. We can then take the Linear Pairwise Correlations of each column (each distillery’s flavor profile) to find the relationship between the flavor profiles of each distillery.

This quantifies how similar the flavor profile of a “Tullibardine” whiskey is to, say, an “Aberfeldy” whiskey. Which could be quite useful. Do you have anyone who is partial to a very particular whiskey, and worried that you might not be able to get that exact one? Or perhaps you are really partial to a particular whiskey, but cautious about branching out to others given the price tag? This allows us to quantify our best “back up!”

We can, and should, do a few basic checks for our transformed data, such as ensuring that the shape is the expected 12 by 86, and checking the first few rows to ensure they take the expected form of flavors for rows and the index for each distillery marking columns.

We see we have to make a few modifications to clean up the new data set, so we perform those and recheck.

We see that the data does follow our expected form: there are 11 flavors represented for 86 whiskies, each flavor is rated from 0-4. The transpose appears to have not had any issues, so we can continue with our analysis.

We might be tempted to analyze this data in the same way as we did the correlation of flavors, but there’s a catch: we now have 86 rows by 12 columns. It becomes much less reasonable to check any given row manually, simply due to the size of the data.

In addition, each flavor profile is now composed of 12 flavors - the mean, standard deviation, and other summary statistics make much less meaningful sense when taken across a flavor profile. We examine the summary statistics for the whiskey from “Tullibardine”.

What does a mean of 1.25 truly indicate in this context? Or an IQR of 0.75-2.00? The minimum value of 0 and maximum value of 3 indicate that the most “intense” flavor is fairly strong and some may be absent, but we get no real understanding of the flavor profile from this at face value.

Now we can create our Linear Correlation Matrix, similarly to how we correlated across individual flavors. This generates the correlation between the flavor profiles of each Distillery.

As a reminder:

We pass a data frame (distilleryProfiles), and R will perform a pairwise correlation among columns, with each column representing the entire flavor profile of a single whiskey.

We are returned a new data frame. Each column still corresponds to each distillery’s flavor profile, but each row indicates which distillery’s flavor profile the column distillery’s flavor profile was associated with rather than an individual whiskey. This association calculates for every aspect of the flavor profile, not just the aggregate “mean” scores.

This allows the computer to perform and return a much more robust association than could be easily performed by hand. For two distilleries to be associated, it is not enough that they have similar total values for all flavors, but that they have related intensity values for each individual flavor that composes that flavor profile.

Values of Pearson Correlation Coefficients range from -1 to 1. Negative values indicate a negative association (indicating the flavor profiles of the distilleries are less similar), while positive values indicate a positive association (indicating flavor profiles of the distilleries are more similar). A value of 0 indicates no relationship. If two distilleries create “opposite” flavor profiles, we would expect them to have a negative association, not no association.

We can interpret this to state that the higher absolute value the Pearson Correlation Coefficient, the stronger the similarity of the overall flavor profiles between the whiskies produced at both distilleries. If you are looking for your “backup” or “next best,” look for a strong positive association with a known whiskey you like. If you are looking for something to avoid, look for a strong negative association with a known whiskey you like.

We store this new DataFrame of correlation values as distilleryCorrelations.

It is important to remember that these associations do not imply direction or causality! As such, the association of “GlenElgin” to “Scapa” is the same as the association of “Scapa” to “GlenElgin”.

In attempting to examine the data for distilleryCorrelations, the size problem from distilleryProfiles is even more magnified - it is an 86 by 86 grid. As we have demonstrated checks for size, and examining the first few rows, we will omit those steps here.

The most benefit for time is found through examination of a visualization of the data.

This is a very, very large and complex image, and it’s hard to read any of the data points unless you have sufficient space on your display.

For simplicity, we can omit the Distillery labels.

As we can see, our Heatmap() provides a wealth of information about our distilleries at a glance.

We can check our data against our expectations at a glance: 1. We have an 86 x 86 product, reflecting that each distillery’s flavor profile was correlated with each other distillery’s flavor profile 2. The diagonal represents consistent values of 1, as each distillery has a 1:1 correlation with itself. 3. The data is mirrored symmetrically across the diagonal, as the associations have no direction.

We can see bands that indicate certain distilleries appear to have opposite flavor profiles, and others appear to be strongly clustered.

There is a particular inclination this visual pattern gives us: it appears to be a “checkerboard pattern.” This indicates that there may be “clusters” we can sort these whiskies into, which could serve as a “short hand” or “label” that indicates these flavor profiles.

Clustering

Scotch whiskies are traditionally clustered by “Region”, with six major regions: Speyside, Highlands, Lowlands, Islands, Campbelltown, and Islay. These regions are geographic, but do they also indicate any association with flavor profile? Do whiskies in the same “Region” have similar flavor profiles, or should we have a different indicator on labels?

We already explored the association between distilleries (by flavor profile) in distilleryCorrelations. Since our original data set also included “Regions” for each whiskey, we can continue our analysis to explore this relationship.

Region Analysis - Unsorted

First, lets get an understanding of the value that “Region” provides us. We can do this with a few rBokeh grid visualizations.

It would be tempting to simply use distillerycorrelations, as this already shows us all of the associations between our distilleries. However, this data is not sorted meaningfully by “Region” and would be difficult to interpret. To solidify this, lets go through the process of visualization without sorting, and compare it to our sorted model.

First, we recognized that the Region variable in the original data set indicated these regions. We can convert this column to a factor to reflect that.

Second, we regonize that our linear correlation matrix has 86 colums and 86 rows, which share the same labels: each of the distilleries, in order. We can use the built in rep() function of R to generate an (x,y) grid representing each Distillery pairing.

We start by converting our Correlations into a list.

We can then build our X and Y values for a rBokeh visualization of our correlations, based on the default method that R used to turn our distilleryCorrelation dataframe into a list. Since our correlations have each Distillery paired with each Distillery, we can use our Distillery values to make this process more streamlined with the rep() function.

rusXs <- rep(whiskey$Distillery,dim(whiskey)[1])
rusYs <- rep(whiskey$Distillery, each=dim(whiskey)[1])
rusData<-data.frame(rusYs,rusXs)
rusData$Correlations<-ruCorr

We want to define colors for each region, as well as specify white for a correlation less than or equal to 0.7, and light grey for a correlation greater than 0.7 but from different regions. We can use the levels() function to get a regions list of each factor level in the Region variable. We can make a list of regionColors the same length. This will let us use the match function to use these as a dictionary.

We use a for loop to assign a color to each of our X and Y pairs in our data set, and add them into our data set

ruColors <- vector(mode="list")
for(i in seq(dim(distilleryCorrelations)[1])){
  #top of nest, i is row number
  for(j in seq(dim(distilleryCorrelations)[1])){
    #nest in second loop, j is column number
    if (distilleryCorrelations[i,j] < 0.7) {
      ruColors <- c(ruColors, white)
    }
    if (distilleryCorrelations[i,j] >= 0.7){
      #get distillery for both column and row whiskies
      rowWhiskeyRegion <- as.character(whiskey[whiskey$Distillery == colnames(distilleryCorrelations)[i],]$Region)
      colWhiskeyRegion <- as.character(whiskey[whiskey$Distillery == colnames(distilleryCorrelations)[j],]$Region)
      #determine if regions are equal
      if (colWhiskeyRegion==rowWhiskeyRegion){
        ruColors <- c(ruColors, regionColors[match(colWhiskeyRegion,regions)])
      }
      if (colWhiskeyRegion!=rowWhiskeyRegion){
        ruColors <- c(ruColors, lightGrey)
      }
    }
  }
}

rusData$Correlations<-ruCorr
rusData$Colors<-ruColors

And finally, we build our rBokeh visualization. Since there are so many values for distillery, we hide our x and y axes labels for readability. We ensure those values are in the hover information. We also set our transparency levels to the strength of the correlation, providing even more information visually.

rusrBokeh <- figure(title="Whiskies by Region - Default") %>%
  ly_crect(rusXs, rusYs, data = rusData, 0.9, 0.9,
           fill_color = ruColors, line_color = ruColors, fill_alpha = ruCorr, line_alpha = ruCorr, hover = "Whiskeys: @rusXs, @rusYs. Correlation: @ruCorr" ) %>%
  x_axis(label="",visible=FALSE) %>% y_axis(label="", visible=FALSE)
rusrBokeh

On examination, we can see the order of the Distilleries is not the same as it is in our Whiskies list! Rather, rBokeh automatically sorted alphabetically. If we want to impose our own structure, we have to define numeric X and Y coordinates.

ruXs <- rep(seq(1:dim(whiskey)[1]), each=dim(whiskey)[1])
ruYs <- rep(seq(1:dim(whiskey)[1]),dim(whiskey)[1])
ruData<-data.frame(ruXs,ruYs)
ruData$ruYdist <- rep(whiskey$Distillery,dim(whiskey)[1])
ruData$ruXdist <- rep(whiskey$Distillery, each=dim(whiskey)[1])
ruData$Correlations<-ruCorr
rusData$Correlations<-ruCorr
rusData$Colors<-ruColors

rurBokeh <- figure(title="Whiskies by Region - Unsorted") %>%
  ly_crect(ruXs, ruYs, data = ruData, 0.9, 0.9,
           fill_color = ruColors, line_color = ruColors, fill_alpha = ruCorr, line_alpha = ruCorr, hover = "Whiskeys: @ruXdist, @ruYdist. Correlation: @ruCorr" ) %>%
  x_axis(label="",visible=FALSE) %>% y_axis(label="", visible=FALSE)
rurBokeh
NA

As we can see from either visualization, it’s difficult to tell if there truly are any meaningful associations between distilleries that share the same “Region” or not. There are several light grey squares. There appear to be several blue squares. But how can we tell if there are “Region” values that have poor association? This is where sorting the data improves our visualization.

Region Analysis - Sorted

The difference in the power of the visualization comes from the pre-processing of the data. The importance of preparing your data properly for any visualization cannot be overstated. A poor pre-processing will weaken any visual impact, while appropriate pre-processing will strengthen arguments you make from your analysis. Lets investigate how sorting our whiskies by “Region” prior to analysis and visualization will increase the impact and clarity of our visualization.

R has a built in method that can sort our data frame by a single column’s value: order(). We specify that we would like to sort our values by “Region”. In inspecting the data, we can dtermine our transformation was successful. The rows of the data were sorted as units, so all data associated with a specific whiskey in our data set remains associated with that same data, it is simply the order that has changed.

We then proceed with an identical creation of our Linear Correlation Matrix, and obtain the new order of the list of distilleries.

regionalWhiskeys <- whiskey[order(whiskey$Region),]
regionalDistilleryProfiles <- regionalWhiskeys[flavorSubsetCols] %>% rotate_df
regionalDistilleryProfiles <- regionalDistilleryProfiles %>% row_to_names(row_number=1)
regionalDistilleryProfiles <- regionalDistilleryProfiles %>% mutate_if(is.character,as.numeric)

regionalDistilleryCorrelations <- cor(regionalDistilleryProfiles,method="pearson")

We construct our x and y variables, correlation variables, and colors just as we did for our unsorted analysis.

rsCorr<-as.list(regionalDistilleryCorrelations)

rsXs <- rep(seq(1:dim(regionalWhiskeys)[1]), each=dim(regionalWhiskeys)[1])
rsYs <- rep(seq(1:dim(regionalWhiskeys)[1]),dim(regionalWhiskeys)[1])
rsData <- data.frame(rsXs,rsYs)
rsData$rsYdist <- rep(regionalWhiskeys$Distillery,dim(regionalWhiskeys)[1])
rsData$rsXdist <- rep(regionalWhiskeys$Distillery, each=dim(regionalWhiskeys)[1])
rsData$rsCorr<-rsCorr

rsColors <- vector(mode="list")
for(i in seq(dim(regionalDistilleryCorrelations)[1])){
  #top of nest, i is row number
  for(j in seq(dim(regionalDistilleryCorrelations)[1])){
    #nest in second loop, j is column number
    if (regionalDistilleryCorrelations[i,j] < 0.7) {
      rsColors <- c(rsColors, white)
    }
    if (regionalDistilleryCorrelations[i,j] >= 0.7){
      #get distillery for both column and row whiskies
      rowWhiskeyRegion <- as.character(regionalWhiskeys[regionalWhiskeys$Distillery == colnames(regionalDistilleryCorrelations)[i],]$Region)
      colWhiskeyRegion <- as.character(regionalWhiskeys[regionalWhiskeys$Distillery == colnames(regionalDistilleryCorrelations)[j],]$Region)
      #determine if regions are equal
      if (colWhiskeyRegion==rowWhiskeyRegion){
        rsColors <- c(rsColors, regionColors[match(colWhiskeyRegion,regions)])
      }
      if (colWhiskeyRegion!=rowWhiskeyRegion){
        rsColors <- c(rsColors, lightGrey)
      }
    }
  }
}

rsData$Colors<-rsColors

And we generate our rBokeh visualization.

rsrBokeh <- figure(title="Whiskies by Region - Sorted") %>%
  ly_crect(rsXs, rsYs, data = rsData, 0.9, 0.9,
           fill_color = rsColors, line_color = rsColors, fill_alpha = rsCorr, line_alpha = rsCorr, hover = "Whiskies: @rsXdist,@rsYdist. Correlation: @rsCorr") %>%
  x_axis(label="",visible=FALSE) %>% y_axis(label="", visible=FALSE)
rsrBokeh

We can see a much clearer picture from this sorted data!

It appears that there are “pockets” of poor correlations within each “Region”, represented by white space. The grey spaces also give us something to think about - are there correlations that are missed by focusing only on “Region”?

LS0tDQp0aXRsZTogIldoaXNraWVzIFdyaXRlLVVwIChSKSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClRoaXMgZXhlcmNpc2Ugd2FzIG9yaWdpbmFsbHkgY29tcGxldGVkIGluIFB5dGhvbiwgZHVyaW5nIG15IGNvbXBsZXRpb24gb2YgV2VlayA0LCBDYXNlIFN0dWR5IDEgb2YgdGhlIGZyZWUgYWNjZXNzIHZlcnNpb24gb2YgSGFydmFyZCBFZFggY291cnNlIFsiVXNpbmcgUHl0aG9uIGZvciBSZXNlYXJjaCJdKGh0dHBzOi8vd3d3LmVkeC5vcmcvY291cnNlL3VzaW5nLXB5dGhvbi1mb3ItcmVzZWFyY2gpIGluIHRoZSBzdW1tZXIgb2YgMjAyMi4gVGhpcyB0cmFuc2xhdGlvbiB0byBSIHdhcyBwZXJmb3JtZWQgaW4gdGhlIFNwcmluZyBvZiAyMDIzLg0KDQpUaGUgW1RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLCBbSmFuaXRvcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2phbml0b3IvaW5kZXguaHRtbCksIFtzam1pc2NdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zam1pc2MvaW5kZXguaHRtbCksIFtyQm9rZWhdKGh0dHBzOi8vaGFmZW4uZ2l0aHViLmlvL3Jib2tlaC8pIGFuZCBbQ29tcGxleEhlYXRtYXBdKGh0dHBzOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvQ29tcGxleEhlYXRtYXAuaHRtbCkgcGFja2FnZXMgd2VyZSBpbnN0YWxsZWQgcHJpb3IgdG8gZ2VuZXJhdGlvbiwgYW5kIHRoaXMgd3JpdGUtdXAgd2FzIGNyZWF0ZWQgYXMgYW4gUm5vdGVib29rIHdpdGggUnN0dWRpby4NCg0KVGhlIHVzZSBvZiBDb21wbGV4SGVhdG1hcCBoYXMgYSByZXF1ZXN0ZWQgY2l0YXRpb24gZnJvbSB0aGUgY3JlYXRvcjoNCg0KR3UsIFouICgyMDE2KSBDb21wbGV4IGhlYXRtYXBzIHJldmVhbCBwYXR0ZXJucyBhbmQgY29ycmVsYXRpb25zIGluIG11bHRpZGltZW5zaW9uYWwgZ2Vub21pYyBkYXRhLiBCaW9pbmZvcm1hdGljcy4gRE9JOiAxMC4xMDkzL2Jpb2luZm9ybWF0aWNzL2J0dzMxMy4NCg0KIyBJbXBvcnRhdGlvbiBvZiBEYXRhDQoNCkxpYnJhcmllcyB1c2VkIGZvciB0aGlzIGRhdGEgc2V0IHdlcmUgaW1wb3J0ZWQuDQoNCmBgYHtyfQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkocmVhZHIpKQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkocmJva2VoKSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGphbml0b3IpKQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoQ29tcGxleEhlYXRtYXApKQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoc2ptaXNjKSkNCmBgYA0KDQpSc3R1ZGlvIGhhcyBhIGZlYXR1cmUgd2hpY2ggbWFrZXMgaW1wb3J0YXRpb24gb2YgZGF0YSBmcm9tIGEgQ1NWIGZpbGUgdG8gYSB0aWJibGUgdXNlciBmcmllbmRseS4gVGhpcyBleGFtcGxlIHdyaXRlIHVwIHdpbGwgc2tpcCB0aGF0IHN0ZXAgdG8gZGVtb25zdHJhdGUgdGhlIGFiaWxpdHkgdG8gbWFudWFsbHkgYXNzZXNzIGltcG9ydGVkIGRhdGEuDQoNClN1cHByZXNzTWVzc2FnZXMgd2FzIHVzZWQgdG8gc3VwcHJlc3MgZGVmYXVsdCBjb25zb2xlIHdhcm5pbmcgbWVzc2FnZXMgdGhhdCB0aGVzZSBmZWF0dXJlcyB3ZXJlIG5vdCB1c2VkLg0KDQpgYGB7cn0NCndoaXNrZXkgPC0gc3VwcHJlc3NNZXNzYWdlcyhzdXBwcmVzc1dhcm5pbmdzKHJlYWRfY3N2KCJodHRwczovL2NvdXJzZXMuZWR4Lm9yZy9hc3NldC12MTpIYXJ2YXJkWCtQSDUyNngrMlQyMDE5K3R5cGVAYXNzZXQrYmxvY2tAd2hpc2tpZXMuY3N2IixzaG93KSkpDQpgYGANCiMgSW50cm9kdWN0b3J5IERlbW9uc3RyYXRpb24gb2YgQm9rZWgNCg0KQm9rZWggYWxsb3dzIGRhdGEgdG8gYmUgdmlzdWFsaXplZCB3aXRoIGludGVyYWN0aXZlIGVsZW1lbnRzIHRoYXQgY2FuIHJlbmRlciBhcyBgLmh0bWxgIGZpbGVzLiBUaGUgZm9sbG93aW5nIGRlbW9uc3RyYXRpb24gaXMgdG8gc2hvdyBhIGdlbmVyYWwgdXNlIG9mIEJva2VoIGJ5IGNyZWF0aW5nIGEgNXg1IGdyaWQgY29tcG9zZWQgb2YgdHdvIGFsdGVybmF0aW5nIGNvbG9ycy4gVGhlcmUgaXMgbm8gcGFydGljdWxhciByZWFzb24gdG8gc2VsZWN0IGEgNXg1IHBsb3QgYXNpZGUgZnJvbSBiZWluZyBhbiBlYXNpbHkgbWFuYWdlYWJsZSBzaXplOyBpdCBpcyB1bnJlbGF0ZWQgdG8gb3VyIFdoaXNraWVzIERhdGFTZXQuIA0KDQpXZSBjYW4gaW5kaWNhdGUgdGhlIHZhbHVlcyB3ZSB3YW50IGZvciBib3RoIG91ciB4IGFuZCB5IGF4ZXMsIGFuZCB0aGUgY29sb3JzIHdlIHdvdWxkIGxpa2UgdG8gdXNlIGZvciBvdXIgZ3JpZC4gQm9rZWggdGFrZXMgaGV4IHZhbHVlcyBmb3IgY29sb3JzLiBgIzAzZjBmY2AgaXMgYSBicmlnaHQgZWxlY3RyaWMgYmx1ZSwgd2hpbGUgYCNmY2ExMDNgIGlzIGEgdmlicmFudCBvcmFuZ2UuIFNlbGVjdGlvbiBpcyBiYXNlZCBvbiBwZXJzb25hbCBwcmVmZXJlbmNlIGFuZCByZWFkYWJpbGl0eS4gU2luY2UgY29sb3JzIGNhbiBiZSBmdWxseSBjdXN0b21pemVkLCB0aGlzIHByb3ZpZGVzIGdyZWF0IGFkYXB0YWJpbGl0eSBmb3IgbW9kaWZpY2F0aW9uIG9mIEJva2VoIHBsb3RzIGZvciBtYXhpbXVtIHJlYWRhYmlsaXR5Lg0KDQpgYGB7cn0NCnZhbHVlcyA8LSBzZXEoMTo1KQ0KY29sb3JzIDwtIGMoIiMwM2YwZmMiLCIjZmNhMTAzIikNCmBgYA0KV2UgYXJlIGFibGUgdG8gdXNlIHRoZSBidWlsdCBpbiBgZXhwYW5kLmdyaWQoKWAgZnVuY3Rpb24gaW4gUiB0byBjcmVhdGUgY2FydGVzaWFuIGNvb3JkaW5hdGVzLCBhbmQgd2UgcmVuYW1lIG91ciBjb2x1bW5zIHRvIGNsYXJpZnkgd2hpY2ggdmFsdWVzIGFyZSBvdXIgeHMgYW5kIHdoaWNoIGFyZSB5cy4NCg0KYGBge3J9DQoNCmdyaWQgPC0gZXhwYW5kLmdyaWQodmFsdWVzLHZhbHVlcykNCmNvbG5hbWVzKGdyaWQpIDwtIGMoInlzIiwieHMiKQ0KYGBgDQoNCldlIHRoZW4gY3JlYXRlIGFsbCB0aGUgY29sb3IgdmFsdWVzIGZvciBvdXIgQm9rZWggcGxvdCwgc3RvcmVkIGluIGBib2tlaENvbG9yc2AsIHVzaW5nIGEgc2ltcGxlIGBmb3JgIGxvb3AuIFRoaXMgbG9vcCBpdGVyYXRlcyBvdmVyIGFsbCB2YWx1ZXMgaW4gYGdyaWRgLCBhbmQgYXNzaWducyB0aGVtIGFsdGVybmF0aW5nIGNvbG9ycyBmcm9tIGBjb2xvcnNgIHVzaW5nIGEgbW9kdWxvIG9wZXJhdG9yIGFuZCBpbmRleGluZy4NCg0KYGBge3J9DQpib2tlaENvbG9ycyA8LSB2ZWN0b3IobW9kZT0ibGlzdCIpDQpmb3IgKGkgaW4gc2VxKGRpbShncmlkKVsxXSkpew0KICBib2tlaENvbG9ycyA8LSBjKGJva2VoQ29sb3JzLGNvbG9yc1soaSUlMikrMV0pDQp9DQpgYGANCg0KRmluYWxseSwgd2UgZGV0ZXJtaW5lIG91ciB0cmFuc3BhcmVuY3kgdmFsdWVzIChgYWxwaGFzYCkgb2YgZWFjaCBwb2ludCwgd2hlcmUgMCBpcyBjb21wbGV0ZWx5IHRyYW5zcGFyZW50IGFuZCAxIGlzIGVudGlyZWx5IG9wYXF1ZS4gVGhlIGJ1aWx0IGluIGBzZXEoKWAgZnVuY3Rpb24gb2YgUiBhbGxvd3MgdXMgdG8gY3JlYXRlIGFuIGV2ZW50bHkgc3BhY2VkIGdyYWRpZW50IG9mIHZhbHVlcywgd2hpY2ggd2lsbCByZXN1bHQgaW4gZGVjcmVhc2luZyB0cmFuc3BhcmVuY3kgZGlhZ29uYWxseSBmcm9tIGJvdHRvbSBsZWZ0IHRvIHRvcCByaWdodDogdGhlIGJvdHRvbSBsZWZ0bW9zdCB2YWx1ZSBgKDEsMSlgIHdpbGwgYmUgdHJhbnNwYXJlbnQsIHdoaWxlIHRoZSB0b3AgcmlnaHRtb3N0IHZhbHVlIGAoNSw1KWAgd2lsbCBiZSBlbnRpcmVseSBvcGFxdWUuDQoNCmBgYHtyfQ0KYWxwaGFzIDwtIHNlcSgwLDEsbGVuZ3RoLm91dD1kaW0oZ3JpZClbMV0pDQpgYGANCg0KVW5saWtlIFB5dGhvbiwgd2UgZG8gbm90IG5lZWQgdG8gZGVjbGFyZSBhIENvbHVtbkRhdGFTb3VyY2UgaW4gUi4gUmF0aGVyLCB3ZSBjYW4gc2ltcGx5IGRlY2xhcmUgYW5kIGNyZWF0ZSBvdXIgZmlndXJlIHVzaW5nIFIncyBidWlsdCBpbiBzeW50YXguDQoNCmBgYHtyfQ0KYmFzaWNCb2tlaCA8LSBmaWd1cmUodGl0bGU9IkJhc2ljIHJCb2tlaCIsIHRvb2xzID0gImhvdmVyIikgJT4lIA0KICBseV9jcmVjdCh4cywgeXMsIGRhdGEgPSBncmlkLCAwLjksIDAuOSwgZmlsbF9jb2xvciA9IGJva2VoQ29sb3JzLCBsaW5lX2NvbG9yID0gYm9rZWhDb2xvcnMsIGZpbGxfYWxwaGEgPSBhbHBoYXMsIGxpbmVfYWxwaGEgPSBhbHBoYXMsIGhvdmVyID0gIlZhbHVlczogQHhzLEB5cyIsIHhsYWI9IiIsIHlsYWI9IiIpDQpiYXNpY0Jva2VoDQpgYGANCg0KQm9rZWggY2FuIGFsc28gYmUgdXRpbGl6ZWQgdG8gcGxvdCB1c2luZyBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlLiBJdCBwbG90cyBzaW1pbGFybHkgdG8gYSBzY2F0dGVyLXBsb3QsIHdoaWNoIGlzIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgbmF0dXJhbGx5IHRha2luZyBmb3JtIGluIGEgY2FydGVzaWFuICh4LHkpIHN0cnVjdHVyZS4gckJva2VoIGFsc28gaGFzIHRoZSBjYXBhYmlsaXR5IHRvIGF1dG9tYXRpY2FsbHkgYXNzaWduIGEgY29uc2lzdGVudCBnbHlwaCBhbmQvb3IgY29sb3Igc3RydWN0dXJlIHRvIGRhdGEgcG9pbnRzLCBiYXNlZCBvbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIFRvIGRlbW9uc3RyYXRlLCBhIGRhdGFmcmFtZSBjb25zaXN0aW5nIG9mICoiWCIqLCAqIlkiKiwgYW5kICoiQ2F0ZWdvcnkiKiB2YWx1ZXMgaXMgZ2VuZXJhdGVkLCBhbmQgdmlzdWFsaXplZCB3aXRoIHJCb2tlaC4NCg0KYGBge3J9DQpzYW1wbGVYcyA8LSBjKDEsNCw2LDgsMyw0LDYpDQpzYW1wbGVZcyA8LSBjKDIsNiw5LDMsMiwxLDUpDQpDYXRlZ29yaWVzPC0gYygiQWxwaGEiLCJCZXRhIiwiRGVsdGEiLCJBbHBoYSIsIkFscGhhIiwiQmV0YSIsIkRlbHRhIikNCnNhbXBsZUNhcnRlc2lhbiA8LSBkYXRhLmZyYW1lIChzYW1wbGVZcyxzYW1wbGVYcyxDYXRlZ29yaWVzKQ0KY29sbmFtZXMoc2FtcGxlQ2FydGVzaWFuKSA8LSBjKCJYLVZhbHVlcyIsIlktVmFsdWVzIiwgIkNhdGVnb3J5IikNCg0KY2FydGVzaWFuUGxvdCA8LSBmaWd1cmUodGl0bGU9IlNhbXBsZSBDYXJ0ZXNpYW4gckJva2VoIiwgdG9vbHM9ImhvdmVyIikgJT4lDQogIGx5X3BvaW50cyhzYW1wbGVYcywgc2FtcGxlWXMsIGRhdGEgPSBzYW1wbGVDYXJ0ZXNpYW4sIGNvbG9yID0gQ2F0ZWdvcmllcywgZ2x5cGggPSBDYXRlZ29yaWVzLCBob3ZlciA9ICJMb2NhdGlvbjogQHNhbXBsZVhzLEBzYW1wbGVZcyIsIHhsYWIgPSAiWC1WYWx1ZXMiLCB5bGFiID0gIlktVmFsdWVzIikNCmNhcnRlc2lhblBsb3QNCmBgYA0KTm90ZSB0aGUgY2hhbmdlIHRvIHRoZSBjb25maWd1cmF0aW9uIG9mIHRoZSAqaG92ZXIqIHRvb2w6IHggYW5kIHkgY29vcmRpbmF0ZXMgYXJlIHN0aWxsIHNob3duIHdoZW4gaG92ZXJpbmcgb3ZlciBhbnkgZGF0YSBwb2ludCwgYnV0IGFyZSBub3cgbGFiZWxlZCBhcyAqKiJMb2NhdGlvbiIqKi4gT2JzZXJ2ZSBob3cgb24gdGhlIHJlbmRlcmVkIHZpc3VhbGl6YXRpb24sIHRoZXJlIGlzIG5vIGluZm9ybWF0aW9uIHByb3ZpZGVkIGJ5ICpob3Zlciogd2hlcmUgYSBkYXRhIHBvaW50IHdhcyBub3QgcGxvdHRlZC4NCg0KIyBXaGlza2V5IEV4cGxvcmF0aW9uDQoNClRoZSBmaXJzdCBzdGFnZSBvZiB1dGlsaXppbmcgYW55IG5ldyBkYXRhIHNldCBzaG91bGQgYmUgZXhwbG9yYXRpb24gb2YgdGhlIGRhdGEgaW5jbHVkZWQgaW4gdGhlIHNldCwgaW5jbHVkaW5nIGl0cyBzb3VyY2UuDQoNCiMjIENsZWFuaW5nIFVwIFRoZSBEYXRhDQoNClRoaXMgZGF0YSBzZXQgd2FzIG9yaWdpbmFsbHkgcHJvdmlkZWQgYXMgYSBjc3YgZmlsZS4gV2UgY29tZSBpbnRvIHRoZSBkYXRhIHNldCB3aXRoIHRoZSBleHBlY3RhdGlvbiB0aGF0IGVhY2ggcm93IGNvbnRhaW5zIGRhdGEgYWJvdXQgYSBzaW5nbGUgd2hpc2tleSBwcm9kdWNlZCBpbiBTY290bGFuZCwgd2l0aCBvbmUgV2hpc2tleSBwZXIgZGlzdGlsbGVyeS4gRWFjaCBXaGlza2V5IGlzICJncmFkZWQiIG9uIHZhcmlvdXMgYXNwZWN0cyBvZiBpdHMgZmxhdm9yIHByb2ZpbGUgKHRob3VnaCwgZnJvbSB0aGUgZGF0YSBzZXQgaXRzZWxmLCB3ZSBkbyBub3Qga25vdyBieSB3aG9tIG9yIHdoYXQgc3BlY2lmaWMgY3JpdGVyaWEgbWF5IGhhdmUgYmVlbiB1c2VkIGZvciBldmFsdWF0aW9uKS4NCg0KRXhwbG9yYXRpb24gb2YgdGhlIGNyZWF0ZWQgYHdoaXNrZXlgIERhdGFGcmFtZSByZXZlYWxzIHRoYXQgdGhlcmUgYXJlIDg2IGluY2x1ZGVkIFdoaXNraWVzLCBhbmQgMTggJ0NvbHVtbnMnIG9mIERhdGEgZm9yIGVhY2ggV2hpc2tleS4NCg0KYGBge3J9DQpkaW0od2hpc2tleSkNCmhlYWQod2hpc2tleSkNCmBgYA0KV2UgY2FuIHNlZSB0aGF0IHRoZSBmaXJzdCByb3cgYXBwZWFycyB0byBpbmRpY2F0ZSBjb2x1bW4gbmFtZXMsIHNvIHdlIGNhbiBzaW1wbGlmeSBvdXIgdGliYmxlLiBUaGVyZSBpcyBhIGZ1bmN0aW9uIGZvciB0aGlzIGluIHRoZSBKYW5pdG9yIHBhY2thZ2UuDQpgYGB7cn0NCndoaXNrZXkgPC0gd2hpc2tleSAlPiUgcm93X3RvX25hbWVzKHJvd19udW1iZXI9MSkNCmhlYWQod2hpc2tleSkNCmBgYA0KDQpXZSB1c2UgYG5hbWVzKHdoaXNrZXkpYCB0byByZXRyaWV2ZSB0aGUgbmFtZXMgb2YgYWxsIGNvbHVtbnMgaW4gdGhlIGRhdGEgc2V0LCB0byBleGFtaW5lIHdoYXQgcG9pbnRzIG9mIGRhdGEgd2UgZXhwZWN0IHRvIGJlIHByb3ZpZGVkIGFib3V0IGVhY2ggd2hpc2tleS4NCg0KYGBge3J9DQpuYW1lcyh3aGlza2V5KQ0KYGBgDQpXZSBzZWUgdGhlIGZpcnN0IHR3byBjb2x1bW5zICoqTkEqKiBhbmQgKipSb3dJRCoqIGFwcGVhciB0byBiZSBhbiBhcmJpdHJhcnkgY291bnQuDQoNClRoZSB0aGlyZCBjb2x1bW4sICoqRGlzdGlsbGVyeSoqLCBhcHBlYXJzIHRvIGJlIHRoZSBuYW1lIG9mIHRoZSBkaXN0aWxsZXJ5IHRoZSB3aGlza2V5IGNhbWUgZnJvbS4gRnJvbSB3aGF0IHdlIGV4cGVjdGVkIGNvbWluZyBpbnRvIG91ciBkYXRhc2V0LCB0aGVyZSBpcyBvbmUgd2hpc2tleSBwZXIgZGlzdGlsbGVyeS4gVGhpcyBtZWFucyB0aGF0ICoqRGlzdGlsbGVyeSoqIGNhbiBzZXJ2ZSBhcyBvdXIgImtleSIuIA0KDQpXZSBmaW5kIHRoYXQgYWxsIGZsYXZvcnMgYXJlIGluY2x1ZGVkIGluIHRoZSBuZXh0IDExIGNvbHVtbnM6ICoqU3dlZXRuZXNzKiosICoqU21va3kqKiwgKipNZWRpY2luYWwqKiwgKipUb2JhY2NvKiosICoqSG9uZXkqKiwgKipTcGljeSoqLCAqKldpbmV5KiosICoqTnV0dHkqKiwgKipNYWx0eSoqLCAqKkZydWl0eSoqLCBhbmQgKipGbG9yYWwqKi4NCg0KV2UgZmluZCB0aGF0IHRoZSByZW1haW5pbmcgY29sdW1ucyBkZXNjcmliZSB0aGUgZ2VvZ3JhcGhpYyBsb2NhdGlvbiBvZiB0aGUgZGlzdGlsbGVyeSB0aGF0IHByb2R1Y2VkIGVhY2ggd2hpc2tleTogKipQb3N0Y29kZSoqLCAqKkxhdGl0dWRlKiosICoqTG9uZ2l0dWRlKiosIGFuZCAqKlJlZ2lvbioqLg0KDQpXZSBjYW4gZHJvcCBib3RoIGNvbHVtbnMgdGhhdCBhcHBlYXIgdG8gc2VydmUgYXMgYSBSb3dJRCwgc2luY2Ugd2UgZG8gbm90IG5lZWQgdGhlbS4gU2ltaWxhcmx5LCB3ZSB3aWxsIGJlIGdlbmVyYXRpbmcgdmFsdWVzIGZvciB0aGUgbGFzdCBjb2x1bW4sICoqR3JvdXAqKiwgYW5kIGNhbiBkcm9wIHRoZSBpbXBvcnRlZCB2YWx1ZXMuIFRoZSBEcGx5ciBwYWNrYWdlIGhhcyBhIGZ1bmN0aW9uIGZvciB0aGlzLg0KYGBge3J9DQp3aGlza2V5IDwtIHNlbGVjdCh3aGlza2V5LC1jKDEsMiwyMCkpDQpoZWFkKHdoaXNrZXkpDQpgYGANClRoYW5rcyB0byBvdXIgdGliYmxlLCB3ZSBtYXkgbm90aWNlIHRoYXQgb3VyIGNvbHVtbnMgYWxsIGFyZSBzdG9yaW5nIHRoZWlyIGRhdGEgYXMgYGNoYXJhY3RlcmBzLiBUaGlzIGlzIGNvbmZpcm1lZCBieSBjaGVja2luZyB0aGUgY2xhc3MgZm9yIGFsbCBvZiBvdXIgY29sdW1ucy4NCmBgYHtyfQ0Kc2FwcGx5KHdoaXNrZXksY2xhc3MpDQpgYGANCkx1Y2tpbHkgRHBseXIgaGFzIGEgbWV0aG9kIHRvIHF1aWNrbHkgY29udmVydCBvdXIgY29sdW1ucyB0aGF0IGNvbnRhaW4gb25seSBudW1lcmljIHZhbHVlcyB0byBhIG51bWVyaWMgZGF0YSBjbGFzcy4NCg0KV2Ugc3BlY2lmeSB3aGljaCBjb2x1bW5zIHdlIHdvdWxkIGxpa2UgdG8gY29udmVydC4gV2Ugc3RvcmUgdGhlIG5hbWVzIG9mIHRoZSBmbGF2b3JzIGluICoqZmxhdm9yKiosIGFuZCBhZGQgKkxhdGl0dWRlKiBhbmQgKkxvbmdpdHVkZSogdG8gYSBsaXN0IG9mIG51bWVyaWMgY29sdW1uIG5hbWVzLg0KYGBge3J9DQpmbGF2b3JzIDwtIG5hbWVzKHdoaXNrZXlbMjoxM10pDQpudW1lcmljcyA8LSBjKGZsYXZvcnMsICJMYXRpdHVkZSIsICJMb25naXR1ZGUiKQ0KDQp3aGlza2V5IDwtIHdoaXNrZXkgJT4lIG11dGF0ZV9hdChmbGF2b3JzLGFzLm51bWVyaWMpDQpoZWFkKHdoaXNrZXkpDQpgYGANClNpbmNlIHdlIGFyZSBwcmltYXJpbHkgaW50ZXJlc3RlZCBpbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZWFjaCBXaGlza2V5IGFuZCBpdHMgZmxhdm9yIHByb2ZpbGUsIHdlIGNhbiBjcmVhdGUgdHdvIHN1Yi1zZXRzIG9mIG91ciBkYXRhIGZyYW1lIHRoYXQgb25seSBjb250YWlucyB0aGUgZmxhdm9ycyBhbmQgRGlzdGlsbGVyeSBuYW1lDQoNCmBgYHtyfQ0KZmxhdm9yU3Vic2V0Q29scyA8LSBjKCJEaXN0aWxsZXJ5IiwgZmxhdm9ycykNCmZsYXZvclByb2ZpbGVzIDwtIHdoaXNrZXlbZmxhdm9yU3Vic2V0Q29sc10NCmhlYWQoZmxhdm9yUHJvZmlsZXMpDQpgYGANCg0KIyMgRXhwbG9yYXRpb24gKEZsYXZvcnMpDQoNCkhvdyBhcmUgdGhlc2UgZmxhdm9yIHByb2ZpbGVzIGNoYXJhY3Rlcml6ZWQ/IEluIG91ciBpbml0aWFsIGV4YW1pbmF0aW9uLCB3ZSBub3RlZCB0aGF0IHRoZXJlIGFwcGVhcmVkIHRvIGJlICppbnRlZ2VyKiB2YWx1ZXMgaW4gZWFjaCBjZWxsIHJlbGF0aW5nIHRvIGFuIGFzcGVjdCBvZiB0aGUgKmZsYXZvciBwcm9maWxlKi4NCg0KV2UgY2FuIGV4YW1pbmUgdGhlc2Ugc2NvcmVzIGluIGEgYml0IG1vcmUgZGV0YWlsLCBzbyB3ZSB1bmRlcnN0YW5kIGhvdyB0aGVzZSB3aGlza2llcyBoYXZlIGJlZW4gY2hhcmFjdGVyaXplZC4NCg0KRmlyc3QsIHdlIGNhbiBpbnNwZWN0IHRoZSBtYXhpbXVtIHZhbHVlcyBvZiBlYWNoICpmbGF2b3IqLiAqKlRvYmFjY28qKiBhcHBlYXJzIHRvIGhhdmUgdGhlIGxvd2VzdCBtYXhpbXVtLCB3aXRoIGEgcGVhayBzY29yZSBvZiAxLiBObyAqZmxhdm9yKiBoYXMgYSBzY29yZSBoaWdoZXIgdGhhbiA0Lg0KYGBge3J9DQpmbGF2b3JQcm9maWxlcyAlPiUgc3VtbWFyaXplX2lmKGlzLm51bWVyaWMsbWF4LG5hLnJtPVRSVUUpDQpgYGANCkluc3BlY3Rpb24gb2YgbWluaW11bSB2YWx1ZXMgc2hvd3MgdGhhdCBlYWNoICpmbGF2b3IqIGhhcyBhIG1pbmltdW0gc2NvcmUgb2YgMCBleGNlcHQgZm9yICoqU3dlZXRuZXNzKiosIHdoaWNoIGhhcyBhIG1pbmltdW0gc2NvcmUgb2YgMS4NCg0KYGBge3J9DQpmbGF2b3JQcm9maWxlcyAlPiUgc3VtbWFyaXplX2lmKGlzLm51bWVyaWMsbWluLG5hLnJtPVRSVUUpDQpgYGANCg0KRWFjaCAqZmxhdm9yKiBhcHBlYXJzIHRvIGJlIHNjb3JlZCBmcm9tIDAgLSA0LCB3aXRoIG5vIGhhbGYgc2NvcmVzLiBGdW5jdGlvbmFsbHksIHRoaXMgaXMgYSAqZGlzY3JldGUgbnVtZXJpYyB2YXJpYWJsZSouIFdoaWxlIHRoZXJlIGlzIG5vIGxlZ2VuZCBwcm92aWRlZCBpbiB0aGUgZGF0YSBzZXQsIGl0IG1heSBiZSBhIHJlYXNvbmFibGUgYXNzdW1wdGlvbiB0byBiZWxpZXZlIHRoYXQgYSBzY29yZSBvZiAwIGluZGljYXRlcyBhICpsYWNrIG9mIGEgZmxhdm9yKiwgd2hpbGUgYSBzY29yZSBvZiA0IGluZGljYXRlcyBhICpzdHJvbmcgZmxhdm9yKiwgd2l0aCBpbnRlcm1lZGlhcnkgc2NvcmVzIGRpdmlkaW5nIHRoZSByZXN1bHRpbmcgY29udGludXVtIG9mIGludGVuc2l0eS4NCg0KV2UgY2FuIHBlcmZvcm0gc29tZSBiYXNpYyBzdW1tYXJ5IHN0YXRpc3RpY3MgdG8gZXhwbG9yZSBlYWNoIG9mIHRoZXNlICpmbGF2b3JzKiBmdXJ0aGVyLCBpZiBkZXNpcmVkLCB1c2luZyB0aGUgYnVpbHQtaW4gYHN1bW1hcnkoKWAgZnVuY3Rpb24gaW4gUi4gV2UgY3JlYXRlIGEgc3Vic2V0IG9mIHRoZSBkYXRhIChgZmxhdm9yRGF0YWApIHRoYXQgZG9lcyBub3QgaGF2ZSB0aGUgKipEaXN0aWxsZXJ5KiogY29sdW1uIHRvIGF2b2lkIHRob3NlIHZhbHVlcyBiZWluZyBjb3VudGVkLg0KDQpXZSBzZWUgdGhhdCBldmVyeSB3aGlza2V5IGhhcyBhIHJhdGluZyBmb3IgZWFjaCAqZmxhdm9yKi4gVGhlIG1lZGlhbiBzY29yZXMgYXBwZWFyIHNpbWlsYXIgdG8gbWVhbnMgd2l0aCBhIGN1cnNvcnkgb3ZlcnZpZXcuDQoNCmBgYHtyfQ0KZmxhdm9yRGF0YSA8LSB3aGlza2V5W2ZsYXZvcnNdDQpzdW1tYXJ5KGZsYXZvckRhdGEpDQpgYGANCiMjIyBFeHBsb3JpbmcgTGluZWFyIENvcnJlbGF0aW9ucw0KIyMjIyBQYWlyd2lzZSBMaW5lYXIgQ29ycmVsYXRpb25zOiBGbGF2b3IgdG8gRmxhdm9yDQoNCk5vdyB0aGF0IHdlIGhhdmUgYSBnZW5lcmFsIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHNoYXBlIGFuZCBmb3JtIG9mIG91ciBkYXRhIHNldCwgd2UgY2FuIGV4cGxvcmUgcmVsYXRpb25zaGlwcyBleHByZXNzZWQgd2l0aGluIGl0LiANCg0KRmlyc3QsIHdlJ2QgbGlrZSB0byBleGFtaW5lIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBpbmRpdmlkdWFsICpmbGF2b3JzKiB0aGF0IGNvbXBvc2UgZWFjaCAqZmxhdm9yIHByb2ZpbGUqLiANCg0KUiBoYXMgYSBidWlsdCBpbiBmdW5jdGlvbiBmb3IgTGluZWFyIENvcnJlbGF0aW9uczogYGNvcigpYC4gDQoNClRoZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgYGNvcigpYCBmdW5jdGlvbiBpcyB3b3J0aCBleGFtaW5pbmc6IHdlIGNhbiBwYXNzIGEgZGF0YSBmcmFtZSwgYW5kIFIgd2lsbCBwZXJmb3JtIGEgcGFpcndpc2UgY29ycmVsYXRpb24gYW1vbmcgY29sdW1ucy4gVGhlIGBmbGF2b3JEYXRhYCBzdWJzZXQgd2lsbCBiZSBtb3N0IHVzZWZ1bCBoZXJlLCBhcyB3ZSBhcmUgY29tcGFyaW5nIGVhY2ggZmxhdm9yIHRvIHRoZSBvdGhlci4NCg0KV2UgYXJlIHJldHVybmVkIGEgbmV3IGRhdGEgZnJhbWUuIEVhY2ggY29sdW1uIHN0aWxsIGNvcnJlc3BvbmRzIHRvIGVhY2ggKmZsYXZvciosIGJ1dCBlYWNoICpyb3cqIGluZGljYXRlcyB3aGljaCAqZmxhdm9yKiB0aGUgY29sdW1uICpmbGF2b3IqIHdhcyBhc3NvY2lhdGVkIHdpdGggcmF0aGVyIHRoYW4gYW4gaW5kaXZpZHVhbCB3aGlza2V5LiANCg0KVmFsdWVzIG9mIFBlYXJzb24gQ29ycmVsYXRpb24gQ29lZmZpY2llbnRzIHJhbmdlIGZyb20gLTEgdG8gMS4gTmVnYXRpdmUgdmFsdWVzIGluZGljYXRlIGEgbmVnYXRpdmUgYXNzb2NpYXRpb24gKGluZGljYXRpbmcgdGhlICpmbGF2b3JzKiBhcmUgKipsZXNzKiogbGlrZWx5IHRvIGFwcGVhciB0b2dldGhlciksIHdoaWxlIHBvc2l0aXZlIHZhbHVlcyBpbmRpY2F0ZSBhIHBvc2l0aXZlIGFzc29jaWF0aW9uIChpbmRpY2F0aW5nICpmbGF2b3JzKiBhcmUgKm1vcmUqIGxpa2VseSB0byBhcHBlYXIgdG9nZXRoZXIpLiBBIHZhbHVlIG9mIDAgaW5kaWNhdGVzICoqbm8gcmVsYXRpb25zaGlwKiouIA0KSWYgZWFjaCAqZmxhdm9yKiB3YXMgc2NvcmVkIGluIGEgYmluYXJ5ICJ5ZXMvbm8iIG9yIDAvMSBmYXNoaW9uLCB3ZSBtaWdodCBleHBlY3QgdGhlIGNvcnJlbGF0aW9uIHRvIG9ubHkgZXh0ZW5kIHRvIHRoZSBwcmVzZW5jZSBvZiBlYWNoICpmbGF2b3IsKiBidXQgYmVjYXVzZSB0aGUgKmZsYXZvcnMqIGFyZSBzY29yZWQgb24gaW50ZW5zaXR5LCB0aGUgaW50ZW5zaXR5IG9mIHRoZSBmbGF2b3JzIGltcGFjdCBvdXIgY29ycmVsYXRpb24gYXMgd2VsbC4gQXMgc3VjaCBlYWNoIGNlbGwgdmFsdWUgaXMgdGhlIFBlYXJzb24gQ29ycmVsYXRpb24gQ29lZmZpY2llbnRzIG9mIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSB2YWx1ZSBvZiB0aGUgKmNvbHVtbiBmbGF2b3IqLCBmYWN0b3JpbmcgaW4gaW50ZW5zaXR5LCB0byB0aGUgKnJvdyBmbGF2b3IqLCBhbHNvIGZhY3RvcmluZyBpbiBpbnRlbnNpdHkuIFdlIGNhbiBpbnRlcnByZXQgdGhpcyB0byBzdGF0ZSB0aGF0ICp0aGUgaGlnaGVyIGFic29sdXRlIHZhbHVlIHRoZSBQZWFyc29uIENvcnJlbGF0aW9uIENvZWZmaWNpZW50LCB0aGUgc3Ryb25nZXIgdGhlIGxpbmsgYmV0d2VlbiB0aGUgaW50ZW5zaXR5IG9mIHRoZSB0d28gZmxhdm9ycyouIA0KDQpXZSBzdG9yZSB0aGlzIG5ldyBkYXRhIGZyYW1lIG9mIGNvcnJlbGF0aW9uIHZhbHVlcyBhcyBgZmxhdm9yQ29ycmVsYXRpb25zYC4NCg0KKipJdCBpcyBpbXBvcnRhbnQgdG8gcmVtZW1iZXIgdGhhdCB0aGVzZSBhc3NvY2lhdGlvbnMgZG8gbm90IGltcGx5IGRpcmVjdGlvbiBvciBjYXVzYWxpdHkhIEFzIHN1Y2gsIHRoZSBhc3NvY2lhdGlvbiBvZiAqIlN3ZWV0bmVzcyIqIHRvICoiSG9uZXkiKiBpcyB0aGUgc2FtZSBhcyB0aGUgYXNzb2NpYXRpb24gb2YgKiJIb25leSIqIHRvICoiU3dlZXRuZXNzIiouKioNCg0KDQpgYGB7cn0NCmZsYXZvckNvcnJlbGF0aW9ucyA8LSBjb3IoZmxhdm9yRGF0YSxtZXRob2Q9InBlYXJzb24iKQ0KYGBgDQpCYXNlZCBvbiBvdXIga25vd2xlZGdlIG9mIFBhaXJ3aXNlIExpbmVhciBDb3JyZWxhdGlvbnMgYW5kIHRoZSBgY29yKClgIGZ1bmN0aW9uLCB3ZSBjYW4gZG8gYSBmZXcgY2hlY2tzIHRvIGVuc3VyZSB3ZSBoYXZlIGFuIG91dHB1dCB0aGF0IGNvbmZvcm1zIHRvIG91ciB0aGVvcmV0aWNhbCBleHBlY3RhdGlvbnMuDQoNCjEuIFdlIGV4cGVjdCBvdXIgZGF0YSB0byBiZSBzeW1tZXRyaWNhbCB3aXRoIDEyIGNvbHVtbnMgYW5kIDEyIHJvd3MuIFRoaXMgaXMgYmVjYXVzZSB3ZSBoYXZlIDEyIGluZGl2aWR1YWwgKmZsYXZvcnMqLiBXZSBjYW4gY2hlY2sgdGhpcyB3aXRoIHRoZSBgZGltKClgIGNvbW1hbmQuDQpgYGB7cn0NCmRpbShmbGF2b3JDb3JyZWxhdGlvbnMpDQpgYGANCjIuIFdlIGNhbiBleHBlY3QgdGhlIGRhdGEgYWxvbmcgdGhlIGRpYWdvbmFsIHRvIHJlcHJlc2VudCBjb25zaXN0ZW50IHZhbHVlcyBvZiAxLiANCg0KVGhpcyBpcyBiZWNhdXNlIGBmbGF2b3JDb3JyZWxhdGlvbnNbMSwxXWAgcmVwcmVzZW50cyB0aGUgc2FtZSAqZmxhdm9yKiBhc3NvY2lhdGVkIHdpdGggaXRzZWxmLiBBcyBlYWNoICpmbGF2b3IqIGlzIG9ubHkgcmF0ZWQgb25jZSBmb3IgZWFjaCB3aGlza2V5LCBpdCBsb2dpY2FsbHkgZm9sbG93cyB0aGF0IHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHNjb3JlcyBmb3IgYW55ICpmbGF2b3IqLCBzdWNoIGFzICoqIlN3ZWV0bmVzcyIqKiB0byAqKiJTd2VldG5lc3MiKiogb3IgKioiU3BpY3kiKiogdG8gKioiU3BpY3kiKiogbXVzdCBiZSAxOjEuDQpgYGB7cn0NCmhlYWQoZmxhdm9yQ29ycmVsYXRpb25zKQ0KYGBgDQozLiBXZSBleHBlY3QgdGhlIGRhdGEgdG8gYmUgbWlycm9yZWQgc3ltbWV0cmljYWxseSBhbG9uZyB0aGUgZGlhZ29uYWwuIA0KDQpUaGlzIGlzIGJlY2F1c2UgYXNzb2NpYXRpb24gZG9lcyBub3QgaGF2ZSBhIGRpcmVjdGlvbi4gQXMgc3VjaCwgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gKioiU3dlZXRuZXNzIioqIGFuZCAqKiJIb25leSIqKiwgdGhlIHNlY29uZCBhbmQgc2l4dGggKmZsYXZvcnMqIChhdCBpbmRleCAyIGFuZCA2IHJlc3BlY3RpdmVseSkgc2hvdWxkIGJlIGZvdW5kIHRvIGJlIHRoZSBzYW1lIHJlZ2FyZGxlc3Mgb2YgdGhlIGRpcmVjdGlvbiBvZiBjb21wYXJpc29uLg0KDQpXZSBjaGVjayB0aGlzIGJ5IGNvbXBhcmluZyB0aGUgdmFsdWVzIGF0IFsyLDZdIGFuZCBbNiwyXSAtIHRoZXkgc2hvdWxkIGJlIGlkZW50aWNhbC4gQW5kIHRoZXkgYXJlLCBib3RoIGhvbGRpbmcgYSB2YWx1ZSBvZiAqKjAuMTMyNTU4MSoqLg0KYGBge3J9DQpwcmludChmbGF2b3JDb3JyZWxhdGlvbnNbMiw2XSkNCnByaW50KGZsYXZvckNvcnJlbGF0aW9uc1s2LDJdKQ0KcHJpbnQoZmxhdm9yQ29ycmVsYXRpb25zWzIsNl0gPT0gZmxhdm9yQ29ycmVsYXRpb25zWzYsMl0pDQpgYGANCldoaWxlIHRoaXMgY29ycmVsYXRpb24gZGF0YSBzZXQsIGF0IDEyIGJ5IDEyLCBpcyBtYW5hZ2VhYmxlLCB0aGlzIHByb2Nlc3MgbmVlZHMgdG8gYmUgc2NhbGVhYmxlLiANCg0KVGhlIENvbXBsZXhIZWF0bWFwIGxpYnJhcnkgcHJvdmlkZXMgdXMgd2l0aCBhIHJvYnVzdCB0b29sIGZvciBjcmVhdGluZyBhIGhlYXRtYXAgb2YgdGhlIGNvcnJlbGF0aW9uIHZhbHVlcy4NCmBgYHtyfQ0KSGVhdG1hcChmbGF2b3JDb3JyZWxhdGlvbnMsIGNvbHVtbl90aXRsZSA9ICJDb3JyZWxhdGlvbnMgb2YgRmxhdm9ycyBpbiBXaGlza2V5cyIsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdChhdCA9IGMoLTEsMCwxKSwgbGFiZWxzID0gYygiTmVnYXRpdmUiLCJOb25lIiwiUG9zaXRpdmUiKSwgdGl0bGUgPSAiQ29ycmVsYXRpb24iKSkNCmBgYA0KQXMgd2UgY2FuIHNlZSwgb3VyIGBIZWF0bWFwYCBwcm92aWRlcyBhIHdlYWx0aCBvZiBpbmZvcm1hdGlvbiBhYm91dCBvdXIgKmZsYXZvcnMqIGF0IGEgZ2xhbmNlLiANCg0KV2UgY2FuIGNoZWNrIG91ciBkYXRhIGFnYWluc3Qgb3VyIGV4cGVjdGF0aW9ucyBhdCBhIGdsYW5jZToNCjEuIFdlIGhhdmUgYW4gMTIgeCAxMiBwcm9kdWN0LCByZWZsZWN0aW5nIHRoYXQgZWFjaCAqZmxhdm9yKiB3YXMgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIgKmZsYXZvcioNCjIuIFRoZSBkaWFnb25hbCByZXByZXNlbnRzIGNvbnNpc3RlbnQgdmFsdWVzIG9mIDEsIGFzIGVhY2ggKmZsYXZvciogaGFzIGEgMToxIGNvcnJlbGF0aW9uIHdpdGggaXRzZWxmLg0KMy4gVGhlIGRhdGEgaXMgbWlycm9yZWQgc3ltbWV0cmljYWxseSBhY3Jvc3MgdGhlIGRpYWdvbmFsLCBhcyB0aGUgYXNzb2NpYXRpb25zIGhhdmUgbm8gZGlyZWN0aW9uLg0KDQpJbiBhbnkgaW5kaXZpZHVhbCAqZmxhdm9yIHByb2ZpbGUqLCBpdCBpcyB1bmxpa2VseSBmb3IgYSB3aGlza2V5IHRvIGhhdmUgKioiU3dlZXRuZXNzIioqLCB3aGlsZSBhbHNvIGJlaW5nIGVpdGhlciAqKiJNZWRpY2luYWwiKiogb3IgKioiU21va3kiKiouIENvbnZlcnNlbHksIGEgd2hpc2tleSB3aXRoICoqIkJvZHkiKiogYXBwZWFycyB0byBiZSBtb3JlIGxpa2VseSB0byBhbHNvIGJlIGNvbnNpZGVyZWQgKioiV2luZXkiKiouDQoNClRoaXMgaXMgYW4gaW50ZXJlc3Rpbmcgb3ZlcnZpZXcgb2YgYWxsIG9mIHRoZSB3aGlza2llcywgYnV0IHdoYXQgZWxzZSBjYW4gd2UgZG8gd2l0aCB0aGlzIGRhdGE/DQojIyMjIFBhaXJ3aXNlIExpbmVhciBDb3JyZWxhdGlvbnM6IERpc3RpbGxlcnkgdG8gRGlzdGlsbGVyeSAoQ29tcGFyaXNvbiBvZiBGbGF2b3IgUHJvZmlsZXMpDQoNClRoZSBkYXRhIHRha2VzIG9uIGEgbXVjaCBtb3JlIGludGVyZXN0aW5nIGZvcm0gZm9yIGFuYWx5c2lzIHdoZW4gd2UgdGFrZSBpdCdzIGB0cmFuc3Bvc2VgLiBXaGVuIHdlIHRha2UgdGhlIGB0cmFuc3Bvc2VgIG9mIGEgZGF0YSBmcmFtZSwgd2UgImZsaXAiIHRoZSBkYXRhIGFjcm9zcyBpdHMgZGlhZ29uYWwgLSByb3dzIGNvbnZlcnQgaW50byBjb2x1bW5zLCBhbmQgdmljZSB2ZXJzYS4NCg0KSW4gb3VyIGBmbGF2b3JQcm9maWxlYCBkYXRhIHNldCwgdGhpcyBvcmdhbml6ZXMgZWFjaCBjb2x1bW4gaW50byB0aGUgKmZsYXZvciBwcm9maWxlKiBvZiBlYWNoIERpc3RpbGxlcnkncyBwcm9kdWN0LiBXZSBjYW4gdGhlbiB0YWtlIHRoZSBMaW5lYXIgUGFpcndpc2UgQ29ycmVsYXRpb25zIG9mIGVhY2ggY29sdW1uIChlYWNoIGRpc3RpbGxlcnkncyBmbGF2b3IgcHJvZmlsZSkgdG8gZmluZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlICpmbGF2b3IgcHJvZmlsZXMgb2YgZWFjaCBkaXN0aWxsZXJ5Ki4NCg0KVGhpcyBxdWFudGlmaWVzIGhvdyBzaW1pbGFyIHRoZSBmbGF2b3IgcHJvZmlsZSBvZiBhICoqIlR1bGxpYmFyZGluZSIqKiB3aGlza2V5IGlzIHRvLCBzYXksIGFuICoqIkFiZXJmZWxkeSIqKiB3aGlza2V5LiBXaGljaCBjb3VsZCBiZSBxdWl0ZSB1c2VmdWwuIERvIHlvdSBoYXZlIGFueW9uZSB3aG8gaXMgcGFydGlhbCB0byBhIHZlcnkgcGFydGljdWxhciB3aGlza2V5LCBhbmQgd29ycmllZCB0aGF0IHlvdSBtaWdodCBub3QgYmUgYWJsZSB0byBnZXQgdGhhdCBleGFjdCBvbmU/IE9yIHBlcmhhcHMgeW91IGFyZSByZWFsbHkgcGFydGlhbCB0byBhIHBhcnRpY3VsYXIgd2hpc2tleSwgYnV0IGNhdXRpb3VzIGFib3V0IGJyYW5jaGluZyBvdXQgdG8gb3RoZXJzIGdpdmVuIHRoZSBwcmljZSB0YWc/IFRoaXMgYWxsb3dzIHVzIHRvIHF1YW50aWZ5IG91ciBiZXN0ICJiYWNrIHVwISINCmBgYHtyfQ0KZGlzdGlsbGVyeVByb2ZpbGVzIDwtIGZsYXZvclByb2ZpbGVzICU+JSByb3RhdGVfZGYNCmBgYA0KV2UgY2FuLCBhbmQgc2hvdWxkLCBkbyBhIGZldyBiYXNpYyBjaGVja3MgZm9yIG91ciB0cmFuc2Zvcm1lZCBkYXRhLCBzdWNoIGFzIGVuc3VyaW5nIHRoYXQgdGhlIHNoYXBlIGlzIHRoZSBleHBlY3RlZCAxMiBieSA4NiwgYW5kIGNoZWNraW5nIHRoZSBmaXJzdCBmZXcgcm93cyB0byBlbnN1cmUgdGhleSB0YWtlIHRoZSBleHBlY3RlZCBmb3JtIG9mICpmbGF2b3JzKiBmb3Igcm93cyBhbmQgdGhlIGluZGV4IGZvciAqZWFjaCBkaXN0aWxsZXJ5IG1hcmtpbmcgY29sdW1ucyouDQoNCmBgYHtyfQ0KZGltKGRpc3RpbGxlcnlQcm9maWxlcykNCmhlYWQoZGlzdGlsbGVyeVByb2ZpbGVzKQ0KYGBgDQpXZSBzZWUgd2UgaGF2ZSB0byBtYWtlIGEgZmV3IG1vZGlmaWNhdGlvbnMgdG8gY2xlYW4gdXAgdGhlIG5ldyBkYXRhIHNldCwgc28gd2UgcGVyZm9ybSB0aG9zZSBhbmQgcmVjaGVjay4NCg0KYGBge3J9DQpkaXN0aWxsZXJ5UHJvZmlsZXMgPC0gZGlzdGlsbGVyeVByb2ZpbGVzICU+JSByb3dfdG9fbmFtZXMocm93X251bWJlcj0xKQ0KDQpkaXN0aWxsZXJ5UHJvZmlsZXMgPC0gZGlzdGlsbGVyeVByb2ZpbGVzICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGFzLm51bWVyaWMpDQoNCmRpbShkaXN0aWxsZXJ5UHJvZmlsZXMpDQpoZWFkKGRpc3RpbGxlcnlQcm9maWxlcykNCmBgYA0KV2Ugc2VlIHRoYXQgdGhlIGRhdGEgZG9lcyBmb2xsb3cgb3VyIGV4cGVjdGVkIGZvcm06IHRoZXJlIGFyZSAxMSBmbGF2b3JzIHJlcHJlc2VudGVkIGZvciA4NiB3aGlza2llcywgZWFjaCBmbGF2b3IgaXMgcmF0ZWQgZnJvbSAwLTQuIFRoZSAqdHJhbnNwb3NlKiBhcHBlYXJzIHRvIGhhdmUgbm90IGhhZCBhbnkgaXNzdWVzLCBzbyB3ZSBjYW4gY29udGludWUgd2l0aCBvdXIgYW5hbHlzaXMuDQoNCldlIG1pZ2h0IGJlIHRlbXB0ZWQgdG8gYW5hbHl6ZSB0aGlzIGRhdGEgaW4gdGhlIHNhbWUgd2F5IGFzIHdlIGRpZCB0aGUgY29ycmVsYXRpb24gb2YgKmZsYXZvcnMqLCBidXQgdGhlcmUncyBhIGNhdGNoOiB3ZSBub3cgaGF2ZSA4NiByb3dzIGJ5IDEyIGNvbHVtbnMuIEl0IGJlY29tZXMgbXVjaCBsZXNzIHJlYXNvbmFibGUgdG8gY2hlY2sgYW55IGdpdmVuIHJvdyBtYW51YWxseSwgc2ltcGx5IGR1ZSB0byB0aGUgc2l6ZSBvZiB0aGUgZGF0YS4NCg0KSW4gYWRkaXRpb24sIGVhY2ggKmZsYXZvciBwcm9maWxlKiBpcyBub3cgY29tcG9zZWQgb2YgMTIgZmxhdm9ycyAtIHRoZSBtZWFuLCBzdGFuZGFyZCBkZXZpYXRpb24sIGFuZCBvdGhlciBzdW1tYXJ5IHN0YXRpc3RpY3MgbWFrZSBtdWNoIGxlc3MgbWVhbmluZ2Z1bCBzZW5zZSB3aGVuIHRha2VuIGFjcm9zcyBhICpmbGF2b3IgcHJvZmlsZSouIFdlIGV4YW1pbmUgdGhlIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgdGhlIHdoaXNrZXkgZnJvbSAqKiJUdWxsaWJhcmRpbmUiKiouIA0KYGBge3J9DQpzdW1tYXJ5KGRpc3RpbGxlcnlQcm9maWxlcyQiVHVsbGliYXJkaW5lIikNCmBgYA0KV2hhdCBkb2VzIGEgbWVhbiBvZiAxLjI1IHRydWx5IGluZGljYXRlIGluIHRoaXMgY29udGV4dD8gT3IgYW4gSVFSIG9mIDAuNzUtMi4wMD8gVGhlIG1pbmltdW0gdmFsdWUgb2YgMCBhbmQgbWF4aW11bSB2YWx1ZSBvZiAzIGluZGljYXRlIHRoYXQgdGhlIG1vc3QgImludGVuc2UiICoqZmxhdm9yKiogaXMgZmFpcmx5IHN0cm9uZyBhbmQgc29tZSBtYXkgYmUgYWJzZW50LCBidXQgd2UgZ2V0IG5vIHJlYWwgdW5kZXJzdGFuZGluZyBvZiB0aGUgKipmbGF2b3IgcHJvZmlsZSoqIGZyb20gdGhpcyBhdCBmYWNlIHZhbHVlLg0KDQpOb3cgd2UgY2FuIGNyZWF0ZSBvdXIgKipMaW5lYXIgQ29ycmVsYXRpb24gTWF0cml4KiosIHNpbWlsYXJseSB0byBob3cgd2UgY29ycmVsYXRlZCBhY3Jvc3MgaW5kaXZpZHVhbCBmbGF2b3JzLiBUaGlzIGdlbmVyYXRlcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgKmZsYXZvciBwcm9maWxlcyogb2YgZWFjaCAqKkRpc3RpbGxlcnkqKi4gDQoNCkFzIGEgcmVtaW5kZXI6DQoNCldlIHBhc3MgYSBkYXRhIGZyYW1lIChgZGlzdGlsbGVyeVByb2ZpbGVzYCksIGFuZCBSIHdpbGwgcGVyZm9ybSBhIHBhaXJ3aXNlIGNvcnJlbGF0aW9uIGFtb25nIGNvbHVtbnMsIHdpdGggZWFjaCBjb2x1bW4gcmVwcmVzZW50aW5nIHRoZSBlbnRpcmUgKmZsYXZvciBwcm9maWxlKiBvZiBhIHNpbmdsZSB3aGlza2V5Lg0KDQpXZSBhcmUgcmV0dXJuZWQgYSBuZXcgZGF0YSBmcmFtZS4gRWFjaCBjb2x1bW4gc3RpbGwgY29ycmVzcG9uZHMgdG8gZWFjaCAqZGlzdGlsbGVyeSdzIGZsYXZvciBwcm9maWxlKiwgYnV0IGVhY2ggKipyb3cqKiBpbmRpY2F0ZXMgd2hpY2ggKmRpc3RpbGxlcnkncyBmbGF2b3IgcHJvZmlsZSogdGhlIGNvbHVtbiAqZGlzdGlsbGVyeSdzIGZsYXZvciBwcm9maWxlKiB3YXMgYXNzb2NpYXRlZCB3aXRoIHJhdGhlciB0aGFuIGFuIGluZGl2aWR1YWwgd2hpc2tleS4gVGhpcyBhc3NvY2lhdGlvbiBjYWxjdWxhdGVzIGZvciAqZXZlcnkgYXNwZWN0IG9mIHRoZSBmbGF2b3IgcHJvZmlsZSwgbm90IGp1c3QgdGhlIGFnZ3JlZ2F0ZSAibWVhbiIgc2NvcmVzKi4gDQoNCioqVGhpcyBhbGxvd3MgdGhlIGNvbXB1dGVyIHRvIHBlcmZvcm0gYW5kIHJldHVybiBhIG11Y2ggbW9yZSByb2J1c3QgYXNzb2NpYXRpb24gdGhhbiBjb3VsZCBiZSBlYXNpbHkgcGVyZm9ybWVkIGJ5IGhhbmQuIEZvciB0d28gZGlzdGlsbGVyaWVzIHRvIGJlIGFzc29jaWF0ZWQsIGl0IGlzIG5vdCBlbm91Z2ggdGhhdCB0aGV5IGhhdmUgc2ltaWxhciB0b3RhbCB2YWx1ZXMgZm9yIGFsbCBmbGF2b3JzLCBidXQgdGhhdCB0aGV5IGhhdmUgcmVsYXRlZCBpbnRlbnNpdHkgdmFsdWVzIGZvciBlYWNoIGluZGl2aWR1YWwgZmxhdm9yIHRoYXQgY29tcG9zZXMgdGhhdCBmbGF2b3IgcHJvZmlsZS4qKg0KDQpWYWx1ZXMgb2YgUGVhcnNvbiBDb3JyZWxhdGlvbiBDb2VmZmljaWVudHMgcmFuZ2UgZnJvbSAtMSB0byAxLiBOZWdhdGl2ZSB2YWx1ZXMgaW5kaWNhdGUgYSBuZWdhdGl2ZSBhc3NvY2lhdGlvbiAoaW5kaWNhdGluZyB0aGUgKmZsYXZvciBwcm9maWxlcyBvZiB0aGUgZGlzdGlsbGVyaWVzKiBhcmUgKipsZXNzKiogc2ltaWxhciksIHdoaWxlIHBvc2l0aXZlIHZhbHVlcyBpbmRpY2F0ZSBhIHBvc2l0aXZlIGFzc29jaWF0aW9uIChpbmRpY2F0aW5nICpmbGF2b3IgcHJvZmlsZXMgb2YgdGhlIGRpc3RpbGxlcmllcyogYXJlICoqbW9yZSoqIHNpbWlsYXIpLiBBIHZhbHVlIG9mIDAgaW5kaWNhdGVzICoqbm8gcmVsYXRpb25zaGlwKiouIElmIHR3byAqKmRpc3RpbGxlcmllcyoqIGNyZWF0ZSAib3Bwb3NpdGUiICpmbGF2b3IgcHJvZmlsZXMqLCB3ZSB3b3VsZCBleHBlY3QgdGhlbSB0byBoYXZlIGEgbmVnYXRpdmUgYXNzb2NpYXRpb24sIG5vdCBubyBhc3NvY2lhdGlvbi4NCg0KIFdlIGNhbiBpbnRlcnByZXQgdGhpcyB0byBzdGF0ZSB0aGF0ICoqdGhlIGhpZ2hlciBhYnNvbHV0ZSB2YWx1ZSB0aGUgUGVhcnNvbiBDb3JyZWxhdGlvbiBDb2VmZmljaWVudCwgdGhlIHN0cm9uZ2VyIHRoZSBzaW1pbGFyaXR5IG9mIHRoZSBvdmVyYWxsIGZsYXZvciBwcm9maWxlcyBiZXR3ZWVuIHRoZSB3aGlza2llcyBwcm9kdWNlZCBhdCBib3RoIGRpc3RpbGxlcmllcyoqLiBJZiB5b3UgYXJlIGxvb2tpbmcgZm9yIHlvdXIgImJhY2t1cCIgb3IgIm5leHQgYmVzdCwiIGxvb2sgZm9yIGEgc3Ryb25nIHBvc2l0aXZlIGFzc29jaWF0aW9uIHdpdGggYSBrbm93biB3aGlza2V5IHlvdSBsaWtlLiBJZiB5b3UgYXJlIGxvb2tpbmcgZm9yIHNvbWV0aGluZyB0byBhdm9pZCwgbG9vayBmb3IgYSBzdHJvbmcgbmVnYXRpdmUgYXNzb2NpYXRpb24gd2l0aCBhIGtub3duIHdoaXNrZXkgeW91IGxpa2UuDQoNCldlIHN0b3JlIHRoaXMgbmV3IERhdGFGcmFtZSBvZiBjb3JyZWxhdGlvbiB2YWx1ZXMgYXMgYGRpc3RpbGxlcnlDb3JyZWxhdGlvbnNgLg0KDQoqKkl0IGlzIGltcG9ydGFudCB0byByZW1lbWJlciB0aGF0IHRoZXNlIGFzc29jaWF0aW9ucyBkbyBub3QgaW1wbHkgZGlyZWN0aW9uIG9yIGNhdXNhbGl0eSEgQXMgc3VjaCwgdGhlIGFzc29jaWF0aW9uIG9mICoiR2xlbkVsZ2luIiogdG8gKiJTY2FwYSIqIGlzIHRoZSBzYW1lIGFzIHRoZSBhc3NvY2lhdGlvbiBvZiAqIlNjYXBhIiogdG8gKiJHbGVuRWxnaW4iKi4qKg0KDQpgYGB7cn0NCmRpc3RpbGxlcnlDb3JyZWxhdGlvbnMgPC0gY29yKGRpc3RpbGxlcnlQcm9maWxlcyxtZXRob2Q9InBlYXJzb24iKQ0KYGBgDQoNCkluIGF0dGVtcHRpbmcgdG8gZXhhbWluZSB0aGUgZGF0YSBmb3IgYGRpc3RpbGxlcnlDb3JyZWxhdGlvbnNgLCB0aGUgc2l6ZSBwcm9ibGVtIGZyb20gYGRpc3RpbGxlcnlQcm9maWxlc2AgaXMgZXZlbiBtb3JlIG1hZ25pZmllZCAtIGl0IGlzIGFuIDg2IGJ5IDg2IGdyaWQuIEFzIHdlIGhhdmUgZGVtb25zdHJhdGVkIGNoZWNrcyBmb3Igc2l6ZSwgYW5kIGV4YW1pbmluZyB0aGUgZmlyc3QgZmV3IHJvd3MsIHdlIHdpbGwgb21pdCB0aG9zZSBzdGVwcyBoZXJlLg0KDQpUaGUgbW9zdCBiZW5lZml0IGZvciB0aW1lIGlzIGZvdW5kIHRocm91Z2ggZXhhbWluYXRpb24gb2YgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBkYXRhLiANCg0KYGBge3J9DQpIZWF0bWFwKGRpc3RpbGxlcnlDb3JyZWxhdGlvbnMsIGNvbHVtbl90aXRsZSA9ICJDb3JyZWxhdGlvbnMgb2YgRGlzdGlsbGVyeSBGbGF2b3IgUHJvZmlsZXMiLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QoYXQgPSBjKC0xLDAsMSksIGxhYmVscyA9IGMoIk5lZ2F0aXZlIiwiTm9uZSIsIlBvc2l0aXZlIiksIHRpdGxlID0gIkNvcnJlbGF0aW9uIiksIHJvd19uYW1lc19tYXhfd2lkdGg9bWF4X3RleHRfd2lkdGgocm93bmFtZXMoZGlzdGlsbGVyeUNvcnJlbGF0aW9ucyksIGdwID0gZ3Bhcihmb250c2l6ZSA9IDEyKSkgKQ0KYGBgDQpUaGlzIGlzIGEgdmVyeSwgdmVyeSBsYXJnZSBhbmQgY29tcGxleCBpbWFnZSwgYW5kIGl0J3MgaGFyZCB0byByZWFkIGFueSBvZiB0aGUgZGF0YSBwb2ludHMgdW5sZXNzIHlvdSBoYXZlIHN1ZmZpY2llbnQgc3BhY2Ugb24geW91ciBkaXNwbGF5Lg0KDQpGb3Igc2ltcGxpY2l0eSwgd2UgY2FuIG9taXQgdGhlICoqRGlzdGlsbGVyeSoqIGxhYmVscy4NCg0KYGBge3J9DQpIZWF0bWFwKGRpc3RpbGxlcnlDb3JyZWxhdGlvbnMsIGNvbHVtbl90aXRsZSA9ICJDb3JyZWxhdGlvbnMgb2YgRGlzdGlsbGVyeSBGbGF2b3IgUHJvZmlsZXMiLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QoYXQgPSBjKC0xLDAsMSksIGxhYmVscyA9IGMoIk5lZ2F0aXZlIiwiTm9uZSIsIlBvc2l0aXZlIiksIHRpdGxlID0gIkNvcnJlbGF0aW9uIiksIHNob3dfcm93X25hbWVzPUZBTFNFLCBzaG93X2NvbHVtbl9uYW1lcyA9IEZBTFNFKQ0KYGBgDQpBcyB3ZSBjYW4gc2VlLCBvdXIgYEhlYXRtYXAoKWAgcHJvdmlkZXMgYSB3ZWFsdGggb2YgaW5mb3JtYXRpb24gYWJvdXQgb3VyICoqZGlzdGlsbGVyaWVzKiogYXQgYSBnbGFuY2UuIA0KDQpXZSBjYW4gY2hlY2sgb3VyIGRhdGEgYWdhaW5zdCBvdXIgZXhwZWN0YXRpb25zIGF0IGEgZ2xhbmNlOg0KMS4gV2UgaGF2ZSBhbiA4NiB4IDg2IHByb2R1Y3QsIHJlZmxlY3RpbmcgdGhhdCBlYWNoICoqZGlzdGlsbGVyeSdzIGZsYXZvciBwcm9maWxlKiogd2FzIGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyICoqZGlzdGlsbGVyeSdzIGZsYXZvciBwcm9maWxlKioNCjIuIFRoZSBkaWFnb25hbCByZXByZXNlbnRzIGNvbnNpc3RlbnQgdmFsdWVzIG9mIDEsIGFzIGVhY2ggKipkaXN0aWxsZXJ5KiogaGFzIGEgMToxIGNvcnJlbGF0aW9uIHdpdGggaXRzZWxmLg0KMy4gVGhlIGRhdGEgaXMgbWlycm9yZWQgc3ltbWV0cmljYWxseSBhY3Jvc3MgdGhlIGRpYWdvbmFsLCBhcyB0aGUgYXNzb2NpYXRpb25zIGhhdmUgbm8gZGlyZWN0aW9uLg0KDQpXZSBjYW4gc2VlIGJhbmRzIHRoYXQgaW5kaWNhdGUgY2VydGFpbiAqKmRpc3RpbGxlcmllcyoqIGFwcGVhciB0byBoYXZlIG9wcG9zaXRlIGZsYXZvciBwcm9maWxlcywgYW5kIG90aGVycyBhcHBlYXIgdG8gYmUgc3Ryb25nbHkgY2x1c3RlcmVkLg0KDQpUaGVyZSBpcyBhIHBhcnRpY3VsYXIgaW5jbGluYXRpb24gdGhpcyB2aXN1YWwgcGF0dGVybiBnaXZlcyB1czogaXQgYXBwZWFycyB0byBiZSBhICIqKmNoZWNrZXJib2FyZCBwYXR0ZXJuKiouIiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZXJlIG1heSBiZSAqKiJjbHVzdGVycyIqKiB3ZSBjYW4gc29ydCB0aGVzZSB3aGlza2llcyBpbnRvLCB3aGljaCBjb3VsZCBzZXJ2ZSBhcyBhICJzaG9ydCBoYW5kIiBvciAibGFiZWwiIHRoYXQgaW5kaWNhdGVzIHRoZXNlIGZsYXZvciBwcm9maWxlcy4NCg0KIyBDbHVzdGVyaW5nDQoNClNjb3RjaCB3aGlza2llcyBhcmUgdHJhZGl0aW9uYWxseSBjbHVzdGVyZWQgYnkgKioiUmVnaW9uIioqLCB3aXRoIHNpeCBtYWpvciByZWdpb25zOiBTcGV5c2lkZSwgSGlnaGxhbmRzLCBMb3dsYW5kcywgSXNsYW5kcywgQ2FtcGJlbGx0b3duLCBhbmQgSXNsYXkuIFRoZXNlIHJlZ2lvbnMgYXJlIGdlb2dyYXBoaWMsIGJ1dCBkbyB0aGV5IGFsc28gaW5kaWNhdGUgYW55IGFzc29jaWF0aW9uIHdpdGggKmZsYXZvciBwcm9maWxlKj8gRG8gd2hpc2tpZXMgaW4gdGhlIHNhbWUgKioiUmVnaW9uIioqIGhhdmUgc2ltaWxhciAqZmxhdm9yIHByb2ZpbGVzKiwgb3Igc2hvdWxkIHdlIGhhdmUgYSBkaWZmZXJlbnQgaW5kaWNhdG9yIG9uIGxhYmVscz8NCg0KV2UgYWxyZWFkeSBleHBsb3JlZCB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiAqZGlzdGlsbGVyaWVzKiAoYnkgKmZsYXZvciBwcm9maWxlKikgaW4gYGRpc3RpbGxlcnlDb3JyZWxhdGlvbnNgLiBTaW5jZSBvdXIgb3JpZ2luYWwgZGF0YSBzZXQgYWxzbyBpbmNsdWRlZCAqKiJSZWdpb25zIioqIGZvciBlYWNoIHdoaXNrZXksIHdlIGNhbiBjb250aW51ZSBvdXIgYW5hbHlzaXMgdG8gZXhwbG9yZSB0aGlzIHJlbGF0aW9uc2hpcC4NCg0KIyMgUmVnaW9uIEFuYWx5c2lzIC0gVW5zb3J0ZWQNCg0KRmlyc3QsIGxldHMgZ2V0IGFuIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHZhbHVlIHRoYXQgKioiUmVnaW9uIioqIHByb3ZpZGVzIHVzLiBXZSBjYW4gZG8gdGhpcyB3aXRoIGEgZmV3IHJCb2tlaCBncmlkIHZpc3VhbGl6YXRpb25zLg0KDQpJdCB3b3VsZCBiZSB0ZW1wdGluZyB0byBzaW1wbHkgdXNlIGBkaXN0aWxsZXJ5Y29ycmVsYXRpb25zYCwgYXMgdGhpcyBhbHJlYWR5IHNob3dzIHVzIGFsbCBvZiB0aGUgYXNzb2NpYXRpb25zIGJldHdlZW4gb3VyICoqZGlzdGlsbGVyaWVzKiouIEhvd2V2ZXIsIHRoaXMgZGF0YSBpcyBub3Qgc29ydGVkIG1lYW5pbmdmdWxseSBieSAqKiJSZWdpb24iKiogYW5kIHdvdWxkIGJlIGRpZmZpY3VsdCB0byBpbnRlcnByZXQuIFRvIHNvbGlkaWZ5IHRoaXMsIGxldHMgZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiB2aXN1YWxpemF0aW9uIHdpdGhvdXQgc29ydGluZywgYW5kIGNvbXBhcmUgaXQgdG8gb3VyIHNvcnRlZCBtb2RlbC4NCg0KRmlyc3QsIHdlIHJlY29nbml6ZWQgdGhhdCB0aGUgKipSZWdpb24qKiB2YXJpYWJsZSBpbiB0aGUgb3JpZ2luYWwgZGF0YSBzZXQgaW5kaWNhdGVkIHRoZXNlIHJlZ2lvbnMuIFdlIGNhbiBjb252ZXJ0IHRoaXMgY29sdW1uIHRvIGEgKmZhY3RvciogdG8gcmVmbGVjdCB0aGF0Lg0KYGBge3J9DQp3aGlza2V5JFJlZ2lvbiA8LSBhcy5mYWN0b3Iod2hpc2tleSRSZWdpb24pDQpgYGANCg0KU2Vjb25kLCB3ZSByZWdvbml6ZSB0aGF0IG91ciBsaW5lYXIgY29ycmVsYXRpb24gbWF0cml4IGhhcyA4NiBjb2x1bXMgYW5kIDg2IHJvd3MsIHdoaWNoIHNoYXJlIHRoZSBzYW1lIGxhYmVsczogZWFjaCBvZiB0aGUgZGlzdGlsbGVyaWVzLCBpbiBvcmRlci4gV2UgY2FuIHVzZSB0aGUgYnVpbHQgaW4gYHJlcCgpYCBmdW5jdGlvbiBvZiBSIHRvIGdlbmVyYXRlIGFuICoqKHgseSkqKiBncmlkIHJlcHJlc2VudGluZyBlYWNoIERpc3RpbGxlcnkgcGFpcmluZy4NCg0KV2Ugc3RhcnQgYnkgY29udmVydGluZyBvdXIgQ29ycmVsYXRpb25zIGludG8gYSBsaXN0Lg0KDQpgYGB7cn0NCnJ1Q29ycjwtYXMubGlzdChkaXN0aWxsZXJ5Q29ycmVsYXRpb25zKQ0KYGBgDQoNCldlIGNhbiB0aGVuIGJ1aWxkIG91ciBYIGFuZCBZIHZhbHVlcyBmb3IgYSByQm9rZWggdmlzdWFsaXphdGlvbiBvZiBvdXIgY29ycmVsYXRpb25zLCBiYXNlZCBvbiB0aGUgZGVmYXVsdCBtZXRob2QgdGhhdCBSIHVzZWQgdG8gdHVybiBvdXIgYGRpc3RpbGxlcnlDb3JyZWxhdGlvbmAgZGF0YWZyYW1lIGludG8gYSBsaXN0LiBTaW5jZSBvdXIgY29ycmVsYXRpb25zIGhhdmUgZWFjaCAqKkRpc3RpbGxlcnkqKiBwYWlyZWQgd2l0aCBlYWNoICoqRGlzdGlsbGVyeSoqLCB3ZSBjYW4gdXNlIG91ciAqKkRpc3RpbGxlcnkqKiB2YWx1ZXMgdG8gbWFrZSB0aGlzIHByb2Nlc3MgbW9yZSBzdHJlYW1saW5lZCB3aXRoIHRoZSBgcmVwKClgIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnJ1c1hzIDwtIHJlcCh3aGlza2V5JERpc3RpbGxlcnksZGltKHdoaXNrZXkpWzFdKQ0KcnVzWXMgPC0gcmVwKHdoaXNrZXkkRGlzdGlsbGVyeSwgZWFjaD1kaW0od2hpc2tleSlbMV0pDQpydXNEYXRhPC1kYXRhLmZyYW1lKHJ1c1lzLHJ1c1hzKQ0KcnVzRGF0YSRDb3JyZWxhdGlvbnM8LXJ1Q29ycg0KYGBgDQoNCldlIHdhbnQgdG8gZGVmaW5lIGNvbG9ycyBmb3IgZWFjaCByZWdpb24sIGFzIHdlbGwgYXMgc3BlY2lmeSAqd2hpdGUqIGZvciBhIGNvcnJlbGF0aW9uIGxlc3MgdGhhbiBvciBlcXVhbCB0byAwLjcsIGFuZCBsaWdodCBncmV5IGZvciBhIGNvcnJlbGF0aW9uIGdyZWF0ZXIgdGhhbiAwLjcgYnV0IGZyb20gZGlmZmVyZW50IHJlZ2lvbnMuIFdlIGNhbiB1c2UgdGhlIGBsZXZlbHMoKWAgZnVuY3Rpb24gdG8gZ2V0IGEgYHJlZ2lvbnNgIGxpc3Qgb2YgZWFjaCAqZmFjdG9yIGxldmVsKiBpbiB0aGUgKipSZWdpb24qKiB2YXJpYWJsZS4gV2UgY2FuIG1ha2UgYSBsaXN0IG9mIGByZWdpb25Db2xvcnNgIHRoZSBzYW1lIGxlbmd0aC4gVGhpcyB3aWxsIGxldCB1cyB1c2UgdGhlIGBtYXRjaGAgZnVuY3Rpb24gdG8gdXNlIHRoZXNlIGFzIGEgZGljdGlvbmFyeS4NCg0KYGBge3J9DQpyZWdpb25zIDwtIGxldmVscyh3aGlza2V5JFJlZ2lvbikNCnJlZ2lvbkNvbG9ycyA8LSBjKCIjY2M3OGJjIiwgIiNkZThmMDUiLCIjZDU1ZTAwIiwiI2NhOTE2MSIsIiMwMjllNzMiLCIjMDE3M2IyIikNCg0Kd2hpdGU8LSIjZmZmZmZmIg0KbGlnaHRHcmV5PC0gIiNkM2QzZDMiDQoNCmBgYA0KDQpXZSB1c2UgYSBmb3IgbG9vcCB0byBhc3NpZ24gYSBjb2xvciB0byBlYWNoIG9mIG91ciBYIGFuZCBZIHBhaXJzIGluIG91ciBkYXRhIHNldCwgYW5kIGFkZCB0aGVtIGludG8gb3VyIGRhdGEgc2V0DQoNCmBgYHtyfQ0KcnVDb2xvcnMgPC0gdmVjdG9yKG1vZGU9Imxpc3QiKQ0KZm9yKGkgaW4gc2VxKGRpbShkaXN0aWxsZXJ5Q29ycmVsYXRpb25zKVsxXSkpew0KICAjdG9wIG9mIG5lc3QsIGkgaXMgcm93IG51bWJlcg0KICBmb3IoaiBpbiBzZXEoZGltKGRpc3RpbGxlcnlDb3JyZWxhdGlvbnMpWzFdKSl7DQogICAgI25lc3QgaW4gc2Vjb25kIGxvb3AsIGogaXMgY29sdW1uIG51bWJlcg0KICAgIGlmIChkaXN0aWxsZXJ5Q29ycmVsYXRpb25zW2ksal0gPCAwLjcpIHsNCiAgICAgIHJ1Q29sb3JzIDwtIGMocnVDb2xvcnMsIHdoaXRlKQ0KICAgIH0NCiAgICBpZiAoZGlzdGlsbGVyeUNvcnJlbGF0aW9uc1tpLGpdID49IDAuNyl7DQogICAgICAjZ2V0IGRpc3RpbGxlcnkgZm9yIGJvdGggY29sdW1uIGFuZCByb3cgd2hpc2tpZXMNCiAgICAgIHJvd1doaXNrZXlSZWdpb24gPC0gYXMuY2hhcmFjdGVyKHdoaXNrZXlbd2hpc2tleSREaXN0aWxsZXJ5ID09IGNvbG5hbWVzKGRpc3RpbGxlcnlDb3JyZWxhdGlvbnMpW2ldLF0kUmVnaW9uKQ0KICAgICAgY29sV2hpc2tleVJlZ2lvbiA8LSBhcy5jaGFyYWN0ZXIod2hpc2tleVt3aGlza2V5JERpc3RpbGxlcnkgPT0gY29sbmFtZXMoZGlzdGlsbGVyeUNvcnJlbGF0aW9ucylbal0sXSRSZWdpb24pDQogICAgICAjZGV0ZXJtaW5lIGlmIHJlZ2lvbnMgYXJlIGVxdWFsDQogICAgICBpZiAoY29sV2hpc2tleVJlZ2lvbj09cm93V2hpc2tleVJlZ2lvbil7DQogICAgICAgIHJ1Q29sb3JzIDwtIGMocnVDb2xvcnMsIHJlZ2lvbkNvbG9yc1ttYXRjaChjb2xXaGlza2V5UmVnaW9uLHJlZ2lvbnMpXSkNCiAgICAgIH0NCiAgICAgIGlmIChjb2xXaGlza2V5UmVnaW9uIT1yb3dXaGlza2V5UmVnaW9uKXsNCiAgICAgICAgcnVDb2xvcnMgPC0gYyhydUNvbG9ycywgbGlnaHRHcmV5KQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KfQ0KDQpydXNEYXRhJENvcnJlbGF0aW9uczwtcnVDb3JyDQpydXNEYXRhJENvbG9yczwtcnVDb2xvcnMNCmBgYA0KDQpBbmQgZmluYWxseSwgd2UgYnVpbGQgb3VyIHJCb2tlaCB2aXN1YWxpemF0aW9uLiBTaW5jZSB0aGVyZSBhcmUgc28gbWFueSB2YWx1ZXMgZm9yIGRpc3RpbGxlcnksIHdlIGhpZGUgb3VyIHggYW5kIHkgYXhlcyBsYWJlbHMgZm9yIHJlYWRhYmlsaXR5LiBXZSBlbnN1cmUgdGhvc2UgdmFsdWVzIGFyZSBpbiB0aGUgYGhvdmVyYCBpbmZvcm1hdGlvbi4gV2UgYWxzbyBzZXQgb3VyIHRyYW5zcGFyZW5jeSBsZXZlbHMgdG8gdGhlIHN0cmVuZ3RoIG9mIHRoZSBjb3JyZWxhdGlvbiwgcHJvdmlkaW5nIGV2ZW4gbW9yZSBpbmZvcm1hdGlvbiB2aXN1YWxseS4NCg0KYGBge3J9DQpydXNyQm9rZWggPC0gZmlndXJlKHRpdGxlPSJXaGlza2llcyBieSBSZWdpb24gLSBEZWZhdWx0IikgJT4lDQogIGx5X2NyZWN0KHJ1c1hzLCBydXNZcywgZGF0YSA9IHJ1c0RhdGEsIDAuOSwgMC45LA0KICAgICAgICAgICBmaWxsX2NvbG9yID0gcnVDb2xvcnMsIGxpbmVfY29sb3IgPSBydUNvbG9ycywgZmlsbF9hbHBoYSA9IHJ1Q29yciwgbGluZV9hbHBoYSA9IHJ1Q29yciwgaG92ZXIgPSAiV2hpc2tleXM6IEBydXNYcywgQHJ1c1lzLiBDb3JyZWxhdGlvbjogQHJ1Q29yciIgKSAlPiUNCiAgeF9heGlzKGxhYmVsPSIiLHZpc2libGU9RkFMU0UpICU+JSB5X2F4aXMobGFiZWw9IiIsIHZpc2libGU9RkFMU0UpDQpydXNyQm9rZWgNCmBgYA0KDQpPbiBleGFtaW5hdGlvbiwgd2UgY2FuIHNlZSB0aGUgb3JkZXIgb2YgdGhlIERpc3RpbGxlcmllcyBpcyBub3QgdGhlIHNhbWUgYXMgaXQgaXMgaW4gb3VyIFdoaXNraWVzIGxpc3QhIFJhdGhlciwgckJva2VoIGF1dG9tYXRpY2FsbHkgc29ydGVkIGFscGhhYmV0aWNhbGx5LiBJZiB3ZSB3YW50IHRvIGltcG9zZSBvdXIgb3duIHN0cnVjdHVyZSwgd2UgaGF2ZSB0byBkZWZpbmUgbnVtZXJpYyBYIGFuZCBZIGNvb3JkaW5hdGVzLg0KDQpgYGB7cn0NCnJ1WHMgPC0gcmVwKHNlcSgxOmRpbSh3aGlza2V5KVsxXSksIGVhY2g9ZGltKHdoaXNrZXkpWzFdKQ0KcnVZcyA8LSByZXAoc2VxKDE6ZGltKHdoaXNrZXkpWzFdKSxkaW0od2hpc2tleSlbMV0pDQpydURhdGE8LWRhdGEuZnJhbWUocnVYcyxydVlzKQ0KcnVEYXRhJHJ1WWRpc3QgPC0gcmVwKHdoaXNrZXkkRGlzdGlsbGVyeSxkaW0od2hpc2tleSlbMV0pDQpydURhdGEkcnVYZGlzdCA8LSByZXAod2hpc2tleSREaXN0aWxsZXJ5LCBlYWNoPWRpbSh3aGlza2V5KVsxXSkNCnJ1RGF0YSRDb3JyZWxhdGlvbnM8LXJ1Q29ycg0KcnVzRGF0YSRDb3JyZWxhdGlvbnM8LXJ1Q29ycg0KcnVzRGF0YSRDb2xvcnM8LXJ1Q29sb3JzDQoNCnJ1ckJva2VoIDwtIGZpZ3VyZSh0aXRsZT0iV2hpc2tpZXMgYnkgUmVnaW9uIC0gVW5zb3J0ZWQiKSAlPiUNCiAgbHlfY3JlY3QocnVYcywgcnVZcywgZGF0YSA9IHJ1RGF0YSwgMC45LCAwLjksDQogICAgICAgICAgIGZpbGxfY29sb3IgPSBydUNvbG9ycywgbGluZV9jb2xvciA9IHJ1Q29sb3JzLCBmaWxsX2FscGhhID0gcnVDb3JyLCBsaW5lX2FscGhhID0gcnVDb3JyLCBob3ZlciA9ICJXaGlza2V5czogQHJ1WGRpc3QsIEBydVlkaXN0LiBDb3JyZWxhdGlvbjogQHJ1Q29yciIgKSAlPiUNCiAgeF9heGlzKGxhYmVsPSIiLHZpc2libGU9RkFMU0UpICU+JSB5X2F4aXMobGFiZWw9IiIsIHZpc2libGU9RkFMU0UpDQpydXJCb2tlaA0KDQpgYGANCg0KQXMgd2UgY2FuIHNlZSBmcm9tIGVpdGhlciB2aXN1YWxpemF0aW9uLCBpdCdzIGRpZmZpY3VsdCB0byB0ZWxsIGlmIHRoZXJlIHRydWx5IGFyZSBhbnkgbWVhbmluZ2Z1bCBhc3NvY2lhdGlvbnMgYmV0d2VlbiAqKmRpc3RpbGxlcmllcyoqIHRoYXQgc2hhcmUgdGhlIHNhbWUgKioiUmVnaW9uIioqIG9yIG5vdC4gVGhlcmUgYXJlIHNldmVyYWwgbGlnaHQgZ3JleSBzcXVhcmVzLiBUaGVyZSBhcHBlYXIgdG8gYmUgc2V2ZXJhbCBibHVlIHNxdWFyZXMuIEJ1dCBob3cgY2FuIHdlIHRlbGwgaWYgdGhlcmUgYXJlICoqIlJlZ2lvbiIqKiB2YWx1ZXMgdGhhdCBoYXZlIHBvb3IgYXNzb2NpYXRpb24/IFRoaXMgaXMgd2hlcmUgc29ydGluZyB0aGUgZGF0YSBpbXByb3ZlcyBvdXIgdmlzdWFsaXphdGlvbi4NCg0KIyMgUmVnaW9uIEFuYWx5c2lzIC0gU29ydGVkDQoNClRoZSBkaWZmZXJlbmNlIGluIHRoZSBwb3dlciBvZiB0aGUgdmlzdWFsaXphdGlvbiBjb21lcyBmcm9tIHRoZSBwcmUtcHJvY2Vzc2luZyBvZiB0aGUgZGF0YS4gVGhlIGltcG9ydGFuY2Ugb2YgcHJlcGFyaW5nIHlvdXIgZGF0YSBwcm9wZXJseSBmb3IgYW55IHZpc3VhbGl6YXRpb24gY2Fubm90IGJlIG92ZXJzdGF0ZWQuIEEgcG9vciBwcmUtcHJvY2Vzc2luZyB3aWxsIHdlYWtlbiBhbnkgdmlzdWFsIGltcGFjdCwgd2hpbGUgYXBwcm9wcmlhdGUgcHJlLXByb2Nlc3Npbmcgd2lsbCBzdHJlbmd0aGVuIGFyZ3VtZW50cyB5b3UgbWFrZSBmcm9tIHlvdXIgYW5hbHlzaXMuIExldHMgaW52ZXN0aWdhdGUgaG93IHNvcnRpbmcgb3VyIHdoaXNraWVzIGJ5ICoqIlJlZ2lvbiIqKiBwcmlvciB0byBhbmFseXNpcyBhbmQgdmlzdWFsaXphdGlvbiB3aWxsIGluY3JlYXNlIHRoZSBpbXBhY3QgYW5kIGNsYXJpdHkgb2Ygb3VyIHZpc3VhbGl6YXRpb24uDQoNClIgaGFzIGEgYnVpbHQgaW4gbWV0aG9kIHRoYXQgY2FuIHNvcnQgb3VyIGRhdGEgZnJhbWUgYnkgYSBzaW5nbGUgY29sdW1uJ3MgdmFsdWU6IGBvcmRlcigpYC4gV2Ugc3BlY2lmeSB0aGF0IHdlIHdvdWxkIGxpa2UgdG8gc29ydCBvdXIgdmFsdWVzIGJ5ICoqIlJlZ2lvbiIqKi4gSW4gaW5zcGVjdGluZyB0aGUgZGF0YSwgd2UgY2FuIGR0ZXJtaW5lIG91ciB0cmFuc2Zvcm1hdGlvbiB3YXMgc3VjY2Vzc2Z1bC4gVGhlIHJvd3Mgb2YgdGhlIGRhdGEgd2VyZSBzb3J0ZWQgYXMgdW5pdHMsIHNvIGFsbCBkYXRhIGFzc29jaWF0ZWQgd2l0aCBhIHNwZWNpZmljIHdoaXNrZXkgaW4gb3VyIGRhdGEgc2V0IHJlbWFpbnMgYXNzb2NpYXRlZCB3aXRoIHRoYXQgc2FtZSBkYXRhLCBpdCBpcyBzaW1wbHkgdGhlIG9yZGVyIHRoYXQgaGFzIGNoYW5nZWQuDQoNCmBgYHtyfQ0KcmVnaW9uYWxXaGlza2V5cyA8LSB3aGlza2V5W29yZGVyKHdoaXNrZXkkUmVnaW9uKSxdDQpgYGANCg0KV2UgdGhlbiBwcm9jZWVkIHdpdGggYW4gaWRlbnRpY2FsIGNyZWF0aW9uIG9mIG91ciBMaW5lYXIgQ29ycmVsYXRpb24gTWF0cml4LCBhbmQgb2J0YWluIHRoZSBuZXcgb3JkZXIgb2YgdGhlIGxpc3Qgb2YgKipkaXN0aWxsZXJpZXMqKi4NCg0KYGBge3J9DQpyZWdpb25hbFdoaXNrZXlzIDwtIHdoaXNrZXlbb3JkZXIod2hpc2tleSRSZWdpb24pLF0NCnJlZ2lvbmFsRGlzdGlsbGVyeVByb2ZpbGVzIDwtIHJlZ2lvbmFsV2hpc2tleXNbZmxhdm9yU3Vic2V0Q29sc10gJT4lIHJvdGF0ZV9kZg0KcmVnaW9uYWxEaXN0aWxsZXJ5UHJvZmlsZXMgPC0gcmVnaW9uYWxEaXN0aWxsZXJ5UHJvZmlsZXMgJT4lIHJvd190b19uYW1lcyhyb3dfbnVtYmVyPTEpDQpyZWdpb25hbERpc3RpbGxlcnlQcm9maWxlcyA8LSByZWdpb25hbERpc3RpbGxlcnlQcm9maWxlcyAlPiUgbXV0YXRlX2lmKGlzLmNoYXJhY3Rlcixhcy5udW1lcmljKQ0KDQpyZWdpb25hbERpc3RpbGxlcnlDb3JyZWxhdGlvbnMgPC0gY29yKHJlZ2lvbmFsRGlzdGlsbGVyeVByb2ZpbGVzLG1ldGhvZD0icGVhcnNvbiIpDQpgYGANCg0KV2UgY29uc3RydWN0IG91ciB4IGFuZCB5IHZhcmlhYmxlcywgY29ycmVsYXRpb24gdmFyaWFibGVzLCBhbmQgY29sb3JzIGp1c3QgYXMgd2UgZGlkIGZvciBvdXIgdW5zb3J0ZWQgYW5hbHlzaXMuDQpgYGB7cn0NCnJzQ29ycjwtYXMubGlzdChyZWdpb25hbERpc3RpbGxlcnlDb3JyZWxhdGlvbnMpDQoNCnJzWHMgPC0gcmVwKHNlcSgxOmRpbShyZWdpb25hbFdoaXNrZXlzKVsxXSksIGVhY2g9ZGltKHJlZ2lvbmFsV2hpc2tleXMpWzFdKQ0KcnNZcyA8LSByZXAoc2VxKDE6ZGltKHJlZ2lvbmFsV2hpc2tleXMpWzFdKSxkaW0ocmVnaW9uYWxXaGlza2V5cylbMV0pDQpyc0RhdGEgPC0gZGF0YS5mcmFtZShyc1hzLHJzWXMpDQpyc0RhdGEkcnNZZGlzdCA8LSByZXAocmVnaW9uYWxXaGlza2V5cyREaXN0aWxsZXJ5LGRpbShyZWdpb25hbFdoaXNrZXlzKVsxXSkNCnJzRGF0YSRyc1hkaXN0IDwtIHJlcChyZWdpb25hbFdoaXNrZXlzJERpc3RpbGxlcnksIGVhY2g9ZGltKHJlZ2lvbmFsV2hpc2tleXMpWzFdKQ0KcnNEYXRhJHJzQ29ycjwtcnNDb3JyDQoNCnJzQ29sb3JzIDwtIHZlY3Rvcihtb2RlPSJsaXN0IikNCmZvcihpIGluIHNlcShkaW0ocmVnaW9uYWxEaXN0aWxsZXJ5Q29ycmVsYXRpb25zKVsxXSkpew0KICAjdG9wIG9mIG5lc3QsIGkgaXMgcm93IG51bWJlcg0KICBmb3IoaiBpbiBzZXEoZGltKHJlZ2lvbmFsRGlzdGlsbGVyeUNvcnJlbGF0aW9ucylbMV0pKXsNCiAgICAjbmVzdCBpbiBzZWNvbmQgbG9vcCwgaiBpcyBjb2x1bW4gbnVtYmVyDQogICAgaWYgKHJlZ2lvbmFsRGlzdGlsbGVyeUNvcnJlbGF0aW9uc1tpLGpdIDwgMC43KSB7DQogICAgICByc0NvbG9ycyA8LSBjKHJzQ29sb3JzLCB3aGl0ZSkNCiAgICB9DQogICAgaWYgKHJlZ2lvbmFsRGlzdGlsbGVyeUNvcnJlbGF0aW9uc1tpLGpdID49IDAuNyl7DQogICAgICAjZ2V0IGRpc3RpbGxlcnkgZm9yIGJvdGggY29sdW1uIGFuZCByb3cgd2hpc2tpZXMNCiAgICAgIHJvd1doaXNrZXlSZWdpb24gPC0gYXMuY2hhcmFjdGVyKHJlZ2lvbmFsV2hpc2tleXNbcmVnaW9uYWxXaGlza2V5cyREaXN0aWxsZXJ5ID09IGNvbG5hbWVzKHJlZ2lvbmFsRGlzdGlsbGVyeUNvcnJlbGF0aW9ucylbaV0sXSRSZWdpb24pDQogICAgICBjb2xXaGlza2V5UmVnaW9uIDwtIGFzLmNoYXJhY3RlcihyZWdpb25hbFdoaXNrZXlzW3JlZ2lvbmFsV2hpc2tleXMkRGlzdGlsbGVyeSA9PSBjb2xuYW1lcyhyZWdpb25hbERpc3RpbGxlcnlDb3JyZWxhdGlvbnMpW2pdLF0kUmVnaW9uKQ0KICAgICAgI2RldGVybWluZSBpZiByZWdpb25zIGFyZSBlcXVhbA0KICAgICAgaWYgKGNvbFdoaXNrZXlSZWdpb249PXJvd1doaXNrZXlSZWdpb24pew0KICAgICAgICByc0NvbG9ycyA8LSBjKHJzQ29sb3JzLCByZWdpb25Db2xvcnNbbWF0Y2goY29sV2hpc2tleVJlZ2lvbixyZWdpb25zKV0pDQogICAgICB9DQogICAgICBpZiAoY29sV2hpc2tleVJlZ2lvbiE9cm93V2hpc2tleVJlZ2lvbil7DQogICAgICAgIHJzQ29sb3JzIDwtIGMocnNDb2xvcnMsIGxpZ2h0R3JleSkNCiAgICAgIH0NCiAgICB9DQogIH0NCn0NCg0KcnNEYXRhJENvbG9yczwtcnNDb2xvcnMNCmBgYA0KDQpBbmQgd2UgZ2VuZXJhdGUgb3VyIHJCb2tlaCB2aXN1YWxpemF0aW9uLg0KYGBge3J9DQpyc3JCb2tlaCA8LSBmaWd1cmUodGl0bGU9IldoaXNraWVzIGJ5IFJlZ2lvbiAtIFNvcnRlZCIpICU+JQ0KICBseV9jcmVjdChyc1hzLCByc1lzLCBkYXRhID0gcnNEYXRhLCAwLjksIDAuOSwNCiAgICAgICAgICAgZmlsbF9jb2xvciA9IHJzQ29sb3JzLCBsaW5lX2NvbG9yID0gcnNDb2xvcnMsIGZpbGxfYWxwaGEgPSByc0NvcnIsIGxpbmVfYWxwaGEgPSByc0NvcnIsIGhvdmVyID0gIldoaXNraWVzOiBAcnNYZGlzdCxAcnNZZGlzdC4gQ29ycmVsYXRpb246IEByc0NvcnIiKSAlPiUNCiAgeF9heGlzKGxhYmVsPSIiLHZpc2libGU9RkFMU0UpICU+JSB5X2F4aXMobGFiZWw9IiIsIHZpc2libGU9RkFMU0UpDQpyc3JCb2tlaA0KYGBgDQoNCldlIGNhbiBzZWUgYSBtdWNoIGNsZWFyZXIgcGljdHVyZSBmcm9tIHRoaXMgc29ydGVkIGRhdGEhDQoNCkl0IGFwcGVhcnMgdGhhdCB0aGVyZSBhcmUgInBvY2tldHMiIG9mIHBvb3IgY29ycmVsYXRpb25zIHdpdGhpbiBlYWNoICoqIlJlZ2lvbiIqKiwgcmVwcmVzZW50ZWQgYnkgd2hpdGUgc3BhY2UuIFRoZSBncmV5IHNwYWNlcyBhbHNvIGdpdmUgdXMgc29tZXRoaW5nIHRvIHRoaW5rIGFib3V0IC0gYXJlIHRoZXJlIGNvcnJlbGF0aW9ucyB0aGF0IGFyZSBtaXNzZWQgYnkgZm9jdXNpbmcgb25seSBvbiAqKiJSZWdpb24iKio/DQo=